diff --git a/AUTHORS b/AUTHORS
index 565fc69..8ac81207 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -717,6 +717,7 @@
 Michael Morrison <codebythepound@gmail.com>
 Michael Müller <michael@fds-team.de>
 Michael Schechter <mike.schechter@gmail.com>
+Michael Smith <sideshowbarker@gmail.com>
 Michaël Zasso <mic.besace@gmail.com>
 Michael Zugelder <michael@zugelder.org>
 Michel Promonet <michel.promonet.1@gmail.com>
diff --git a/DEPS b/DEPS
index 6d60bdf5..b9f9f00 100644
--- a/DEPS
+++ b/DEPS
@@ -204,11 +204,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'da076e9aca262bbb6f4373258adafdb52e6255bc',
+  'skia_revision': 'be54c66cdd08df96b5170c574d535730eeec4b2f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '05696889ed8ad6708b18bdfbabe603d04a0d76da',
+  'v8_revision': '380d3d23acddd8b6eb5521fbf5cf30b9ff8ac301',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -216,11 +216,11 @@
   # 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': '8844599fba5e3938e63a794cd16e0e342344d628',
+  'angle_revision': '44b9579cf7b8d35cac13f9e2d70c57791ddfd0e2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '023914a27b634063766fee96f2e6763513f5b059',
+  'swiftshader_revision': 'a2fe8e1b2111e69d8846991f9d9bcc5c49505d1c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -275,7 +275,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': 'fa0ace78a3bce6cf9bcc92fb24babd23d0ba92da',
+  'catapult_revision': '7a3a7363a7c321684eb327d1b8aa536986a12ff8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -283,7 +283,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': '91cd7298457949e00edae801664b6553ab805c8c',
+  'devtools_frontend_revision': 'e7b220d512b2debe4621adcd571685f3ec4df7e5',
   # 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.
@@ -351,7 +351,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
-  'nearby_revision': 'bf22c7886559ba24d60c1ca35a13702f3c2c3686',
+  'nearby_revision': '8fafd3ef09818e1983945aa8be9663713c79c142',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -697,7 +697,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': '4GScMzaFl3PhZCqBnZzx-cS6gB6CgHUxjF0lA3-GnBUC',
+          'version': 'suQhvpKvL46vk2RYCR_Hj2EclqgQ84rsinZYd6WndqMC',
       },
     ],
     'condition': 'checkout_android',
@@ -910,7 +910,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '0b1154cfa33cf846ad3b4cb223b249705139bdcc',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '65448dbe5b0d150bb2ad11b15c4330ab1f0b4483',
       'condition': 'checkout_chromeos',
   },
 
@@ -925,12 +925,12 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '62e9bf6678fbf8167aca1f5303e9df8c536b9479',
+      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '0128791edc46285ca4bc876b23dafac7825eea70',
       'condition': 'checkout_linux',
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '7e3ad9eeb839c06b5056ba6a800a7895b927be4f',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'c2c576e9404d0a8fd14649f79fff13ea0d46b4e1',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -982,7 +982,7 @@
     Var('chromium_git') + '/external/github.com/google/gemmlowp.git' + '@' + 'fda83bdc38b118cc6b56753bd540caa49e570745',
 
   'src/third_party/grpc/src': {
-      'url': Var('chromium_git') + '/external/github.com/grpc/grpc.git' + '@' + '4ac9c6f755463a2321f84b0cb2d631e1828faedb',
+      'url': Var('chromium_git') + '/external/github.com/grpc/grpc.git' + '@' + '3ca079faadfcc1f111b6c9a3f3fb10f4b5c794ea',
   },
 
   'src/third_party/freetype/src':
@@ -1302,7 +1302,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '37518c39e3db120322359090db3d54f6ac100078',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'c600d9d76c9f4dacf63073cc0971779cef567fa2',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1576,7 +1576,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': 'fRRnk-3C4PCCGCwZ4hZCeqm9_BnqznTyThRH4XqRVIIC',
+          'version': 'X8zqgmHchz43Cvw6xwn3VRL5keLdpA3cD_DqweHLRqMC',
         },
       ],
       'dep_type': 'cipd',
@@ -1586,7 +1586,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': 'RYk6BCseAqTGvBlAje0BUCgAxE4sSgM0ydi7gMDe2GkC',
+          'version': 'tC_p4_R9hLfNMczJQbjawF5kzgXRVNF-XVcN7bJxrEYC',
         },
       ],
       'dep_type': 'cipd',
@@ -1596,7 +1596,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-amd64',
-          'version': 'OLq_hZKAgzddlvSP4YR1TK0_2hzZipwLepMP3lOfS28C',
+          'version': '9B5ePukmYbotGUjJuMV-DdjKGW5K0MtKrz8eOpXftu8C',
         },
       ],
       'dep_type': 'cipd',
@@ -1610,7 +1610,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@fcb7de2082e2684220808ff2caa7dff243de83dd',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@70f6de137da5c577a576ea0bf0fb847b53504dca',
     'condition': 'checkout_src_internal',
   },
 
@@ -1629,7 +1629,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'fZQVsch8-pFrKQYES9hXWDDG3REcUWuGXwmmVDiDjIgC',
+        'version': 'HW5L1gDfZ22CD8RHQCF5693hF4adAjqw9dsl2GbuPhkC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1640,7 +1640,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'WW6coVuevgcsm8Aew4K1eh2I1colIiYcRhAJmEAgeMAC',
+        'version': 'pi3u2ETvT9hiAv6aFmCL-AMpn1uuem_dXgvMPPTHEFEC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 827e318..1cb233b 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -2441,6 +2441,8 @@
                     r"^extensions[\\/]renderer[\\/]logging_native_handler\.cc$",
                     r"^fuchsia[\\/]engine[\\/]browser[\\/]frame_impl.cc$",
                     r"^fuchsia[\\/]engine[\\/]context_provider_main.cc$",
+                    # TODO(https://crbug.com/1181062): Temporary debugging.
+                    r"^fuchsia[\\/]engine[\\/]renderer[\\/]web_engine_render_frame_observer.cc$",
                     r"^headless[\\/]app[\\/]headless_shell\.cc$",
                     r"^ipc[\\/]ipc_logging\.cc$",
                     r"^native_client_sdk[\\/]",
diff --git a/WATCHLISTS b/WATCHLISTS
index 0730918..a27eaa4 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1662,7 +1662,7 @@
     },
     'scanning': {
       'filepath': 'chromeos/components/scanning/'\
-                  '|chrome/browser/chromeos/scanning/',
+                  '|chrome/browser/ash/scanning/',
     },
     'screen_orientation': {
       'filepath': 'screen_orientation',
@@ -2230,7 +2230,6 @@
     'blink_css_fragmentation_tests': ['mstensho@chromium.org'],
     'blink_css_grid_layout': ['jfernandez@igalia.com',
                               'obrufau@igalia.com',
-                              'rego@igalia.com',
                               'svillar@igalia.com'],
     'blink_custom_elements': ['dominicc+watchlist@chromium.org'],
     'blink_device_orientation': ['juncai+watch@chromium.org',
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index 03985a7..5ded435 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -530,6 +530,7 @@
     "//components/autofill/android:autofill_java",
     "//components/autofill/android/provider:java",
     "//components/background_task_scheduler:background_task_scheduler_task_ids_java",
+    "//components/component_updater/android:embedded_component_loader_java",
     "//components/content_capture/android:java",
     "//components/crash/android:handler_java",
     "//components/crash/android:java",
@@ -897,7 +898,7 @@
 grit("generate_aw_strings") {
   source = "ui/aw_strings.grd"
   outputs = [ "grit/aw_strings.h" ]
-  foreach(_locale, locales_with_fake_bidi) {
+  foreach(_locale, locales_with_pseudolocales) {
     outputs += [ "aw_strings_${_locale}.pak" ]
   }
 }
@@ -1064,7 +1065,7 @@
       process_file_template(
           android_bundle_locales_as_resources,
           [ "java/res/values-{{source_name_part}}/components_strings.xml" ]) +
-      process_file_template(locales_with_fake_bidi,
+      process_file_template(locales_with_pseudolocales,
                             [ "components_strings_{{source_name_part}}.pak" ])
 }
 
diff --git a/android_webview/browser/BUILD.gn b/android_webview/browser/BUILD.gn
index 9d6ffa9..41991c8 100644
--- a/android_webview/browser/BUILD.gn
+++ b/android_webview/browser/BUILD.gn
@@ -100,6 +100,10 @@
     "aw_web_contents_view_delegate.h",
     "aw_web_ui_controller_factory.cc",
     "aw_web_ui_controller_factory.h",
+    "component_updater/registration.cc",
+    "component_updater/registration.h",
+    "component_updater/trust_token_key_commitments_component_loader.cc",
+    "component_updater/trust_token_key_commitments_component_loader.h",
     "cookie_manager.cc",
     "cookie_manager.h",
     "find_helper.cc",
@@ -193,6 +197,8 @@
     "//components/autofill/android/provider",
     "//components/autofill/content/browser",
     "//components/cdm/browser",
+    "//components/component_updater/android:embedded_component_loader",
+    "//components/component_updater/android:loader_policies",
     "//components/content_capture/android",
     "//components/content_capture/browser",
     "//components/embedder_support/android:util",
diff --git a/android_webview/browser/DEPS b/android_webview/browser/DEPS
index af9cd543..528342ad 100644
--- a/android_webview/browser/DEPS
+++ b/android_webview/browser/DEPS
@@ -13,6 +13,7 @@
   "+components/autofill/core/browser",
   "+components/autofill/core/common",
   "+components/cdm/browser",
+  "+components/component_updater/android",
   "+components/crash/content/browser",
   "+components/crash/core",
   "+components/download/public/common",
diff --git a/android_webview/browser/aw_browser_process.cc b/android_webview/browser/aw_browser_process.cc
index 208d7c1..00cff69 100644
--- a/android_webview/browser/aw_browser_process.cc
+++ b/android_webview/browser/aw_browser_process.cc
@@ -5,14 +5,17 @@
 #include "android_webview/browser/aw_browser_process.h"
 
 #include "android_webview/browser/aw_browser_context.h"
+#include "android_webview/browser/component_updater/registration.h"
 #include "android_webview/browser/lifecycle/aw_contents_lifecycle_notifier.h"
 #include "android_webview/browser/metrics/visibility_metrics_logger.h"
 #include "android_webview/browser_jni_headers/AwBrowserProcess_jni.h"
 #include "android_webview/common/crash_reporter/crash_keys.h"
+#include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
 #include "base/base_paths_posix.h"
 #include "base/path_service.h"
 #include "base/task/thread_pool.h"
+#include "components/component_updater/android/component_loader_policy.h"
 #include "components/crash/core/common/crash_key.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -224,4 +227,11 @@
   crash_key.Set(ConvertJavaStringToUTF8(env, processName));
 }
 
+static base::android::ScopedJavaLocalRef<jobjectArray>
+JNI_AwBrowserProcess_GetComponentLoaderPolicies(JNIEnv* env) {
+  return component_updater::AndroidComponentLoaderPolicy::
+      ToJavaArrayOfAndroidComponentLoaderPolicy(env,
+                                                GetComponentLoaderPolicies());
+}
+
 }  // namespace android_webview
diff --git a/android_webview/browser/component_updater/registration.cc b/android_webview/browser/component_updater/registration.cc
new file mode 100644
index 0000000..54fc5a7
--- /dev/null
+++ b/android_webview/browser/component_updater/registration.cc
@@ -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.
+
+#include "android_webview/browser/component_updater/registration.h"
+
+#include "android_webview/browser/component_updater/trust_token_key_commitments_component_loader.h"
+
+namespace android_webview {
+
+component_updater::ComponentLoaderPolicyVector GetComponentLoaderPolicies() {
+  component_updater::ComponentLoaderPolicyVector policies;
+  LoadTrustTokenKeyCommitmentsComponent(&policies);
+  return policies;
+}
+
+}  // namespace android_webview
diff --git a/android_webview/browser/component_updater/registration.h b/android_webview/browser/component_updater/registration.h
new file mode 100644
index 0000000..66e40d9c
--- /dev/null
+++ b/android_webview/browser/component_updater/registration.h
@@ -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.
+
+#ifndef ANDROID_WEBVIEW_BROWSER_COMPONENT_UPDATER_REGISTRATION_H_
+#define ANDROID_WEBVIEW_BROWSER_COMPONENT_UPDATER_REGISTRATION_H_
+
+#include "components/component_updater/android/component_loader_policy.h"
+
+namespace android_webview {
+
+// ComponentLoaderPolicies for component to load in an embedded WebView during
+// startup.
+component_updater::ComponentLoaderPolicyVector GetComponentLoaderPolicies();
+
+}  // namespace android_webview
+
+#endif  // ANDROID_WEBVIEW_BROWSER_COMPONENT_UPDATER_REGISTRATION_H_
\ No newline at end of file
diff --git a/android_webview/browser/component_updater/trust_token_key_commitments_component_loader.cc b/android_webview/browser/component_updater/trust_token_key_commitments_component_loader.cc
new file mode 100644
index 0000000..3eedac6
--- /dev/null
+++ b/android_webview/browser/component_updater/trust_token_key_commitments_component_loader.cc
@@ -0,0 +1,42 @@
+// 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 "android_webview/browser/component_updater/trust_token_key_commitments_component_loader.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "base/logging.h"
+#include "components/component_updater/android/loader_policies/trust_token_key_commitments_component_loader_policy.h"
+#include "content/public/browser/network_service_instance.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+
+namespace android_webview {
+
+// Add trust tokens ComponentLoaderPolicy to the given policies vector, if Trust
+// Tokens is enabled.
+void LoadTrustTokenKeyCommitmentsComponent(
+    ComponentLoaderPolicyVector* policies) {
+  if (!base::FeatureList::IsEnabled(network::features::kTrustTokens))
+    return;
+
+  DVLOG(1)
+      << "Registering Trust Token Key Commitments component for loading in "
+         "embedded WebView.";
+
+  policies->push_back(
+      std::make_unique<
+          component_updater::TrustTokenKeyCommitmentsComponentLoaderPolicy>(
+          /* on_commitments_ready = */ base::BindRepeating(
+              [](const std::string& raw_commitments) {
+                content::GetNetworkService()->SetTrustTokenKeyCommitments(
+                    raw_commitments, /* done_callback = */ base::DoNothing());
+              })));
+}
+
+}  // namespace android_webview
diff --git a/android_webview/browser/component_updater/trust_token_key_commitments_component_loader.h b/android_webview/browser/component_updater/trust_token_key_commitments_component_loader.h
new file mode 100644
index 0000000..ccff9b8
--- /dev/null
+++ b/android_webview/browser/component_updater/trust_token_key_commitments_component_loader.h
@@ -0,0 +1,24 @@
+// 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 ANDROID_WEBVIEW_BROWSER_COMPONENT_UPDATER_TRUST_TOKEN_KEY_COMMITMENTS_COMPONENT_LOADER_H_
+#define ANDROID_WEBVIEW_BROWSER_COMPONENT_UPDATER_TRUST_TOKEN_KEY_COMMITMENTS_COMPONENT_LOADER_H_
+
+#include <vector>
+
+namespace component_updater {
+class ComponentLoaderPolicy;
+}  // namespace component_updater
+
+namespace android_webview {
+
+using ComponentLoaderPolicyVector =
+    std::vector<std::unique_ptr<component_updater::ComponentLoaderPolicy>>;
+
+void LoadTrustTokenKeyCommitmentsComponent(
+    ComponentLoaderPolicyVector* policies);
+
+}  // namespace android_webview
+
+#endif  // ANDROID_WEBVIEW_BROWSER_COMPONENT_UPDATER_TRUST_TOKEN_KEY_COMMITMENTS_COMPONENT_LOADER_H_
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index 728328f..db584ef 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -42,6 +42,10 @@
 const base::Feature kWebViewExtraHeadersSameOriginOnly{
     "WebViewExtraHeadersSameOriginOnly", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enable the new Java/JS Bridge code path with mojo implementation.
+const base::Feature kWebViewJavaJsBridgeMojo{"WebViewJavaJsBridgeMojo",
+                                             base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Measure the number of pixels occupied by one or more WebViews as a
 // proportion of the total screen size. Depending on the number of
 // WebVieaws and the size of the screen this might be expensive so
@@ -50,6 +54,10 @@
 const base::Feature kWebViewMeasureScreenCoverage{
     "WebViewMeasureScreenCoverage", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Field trial feature for controlling support of Origin Trials on WebView.
+const base::Feature kWebViewOriginTrials{"WebViewOriginTrials",
+                                         base::FEATURE_DISABLED_BY_DEFAULT};
+
 // A Feature used for WebView variations tests. Not used in production.
 const base::Feature kWebViewTestFeature{"WebViewTestFeature",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
@@ -59,9 +67,5 @@
 const base::Feature kWebViewWideColorGamutSupport{
     "WebViewWideColorGamutSupport", base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Enable the new Java/JS Bridge code path with mojo implementation.
-const base::Feature kWebViewJavaJsBridgeMojo{"WebViewJavaJsBridgeMojo",
-                                             base::FEATURE_DISABLED_BY_DEFAULT};
-
 }  // namespace features
 }  // namespace android_webview
diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h
index eead5c0ce..221cb22 100644
--- a/android_webview/common/aw_features.h
+++ b/android_webview/common/aw_features.h
@@ -19,11 +19,12 @@
 extern const base::Feature kWebViewCpuAffinityRestrictToLittleCores;
 extern const base::Feature kWebViewDisplayCutout;
 extern const base::Feature kWebViewExtraHeadersSameOriginOnly;
+extern const base::Feature kWebViewJavaJsBridgeMojo;
 extern const base::Feature kWebViewMeasureScreenCoverage;
 extern const base::Feature kWebViewMixedContentAutoupgrades;
+extern const base::Feature kWebViewOriginTrials;
 extern const base::Feature kWebViewTestFeature;
 extern const base::Feature kWebViewWideColorGamutSupport;
-extern const base::Feature kWebViewJavaJsBridgeMojo;
 
 }  // namespace features
 }  // namespace android_webview
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
index 67cfc3d..28c8cd6f 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
@@ -175,6 +175,10 @@
             AwBrowserProcess.start();
             AwBrowserProcess.handleMinidumpsAndSetMetricsConsent(true /* updateMetricsConsent */);
 
+            // This has to be done after variations are initialized, so components could be
+            // registered or not depending on the variations flags.
+            AwBrowserProcess.loadComponents();
+
             mSharedStatics = new SharedStatics();
             if (BuildInfo.isDebugAndroid()) {
                 mSharedStatics.setWebContentsDebuggingEnabledUnconditionally(true);
diff --git a/android_webview/java/DEPS b/android_webview/java/DEPS
index 661c044..c5c99542 100644
--- a/android_webview/java/DEPS
+++ b/android_webview/java/DEPS
@@ -4,6 +4,7 @@
   "+components/navigation_interception/android/java",
   "+components/policy/android/java",
   "+components/safe_browsing/android/java",
+  "+components/component_updater/android/java",
 
   "-content/public/android/java",
   "+content/public/android/java/src/org/chromium/content_public",
diff --git a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
index 2f5268d..2101c699 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
@@ -46,6 +46,8 @@
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskRunner;
 import org.chromium.base.task.TaskTraits;
+import org.chromium.components.component_updater.ComponentLoaderPolicyBridge;
+import org.chromium.components.component_updater.EmbeddedComponentLoader;
 import org.chromium.components.minidump_uploader.CrashFileManager;
 import org.chromium.components.policy.CombinedPolicyProvider;
 import org.chromium.content_public.browser.BrowserStartupController;
@@ -57,6 +59,7 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -483,11 +486,31 @@
         }
     }
 
+    /**
+     * Load components files from {@link
+     * org.chromium.android_webview.services.ComponentsProviderService}.
+     */
+    public static void loadComponents() {
+        ComponentLoaderPolicyBridge[] componentPolicies =
+                AwBrowserProcessJni.get().getComponentLoaderPolicies();
+        // Don't connect to the service if there are no components to load.
+        if (componentPolicies.length == 0) {
+            return;
+        }
+        EmbeddedComponentLoader loader =
+                new EmbeddedComponentLoader(Arrays.asList(componentPolicies));
+        final Intent intent = new Intent();
+        intent.setClassName(
+                ContextUtils.getApplicationContext(), ServiceNames.AW_COMPONENTS_PROVIDER_SERVICE);
+        loader.connect(intent);
+    }
+
     // Do not instantiate this class.
     private AwBrowserProcess() {}
 
     @NativeMethods
     interface Natives {
         void setProcessNameCrashKey(String processName);
+        ComponentLoaderPolicyBridge[] getComponentLoaderPolicies();
     }
 }
diff --git a/android_webview/javatests/DEPS b/android_webview/javatests/DEPS
index e68018a..161bc46 100644
--- a/android_webview/javatests/DEPS
+++ b/android_webview/javatests/DEPS
@@ -2,6 +2,7 @@
   "+components/autofill/android/java",
   "+components/autofill/android/provider",
   "+components/background_task_scheduler/android/java",
+  "+components/component_updater/android/java",
   "+components/minidump_uploader/android/java",
   "+components/minidump_uploader/android/javatests",
   "+components/policy/android/java",
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/component_updater/EmbeddedComponentLoaderTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/component_updater/EmbeddedComponentLoaderTest.java
new file mode 100644
index 0000000..c6cedf9
--- /dev/null
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/component_updater/EmbeddedComponentLoaderTest.java
@@ -0,0 +1,136 @@
+// 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.
+
+package org.chromium.android_webview.test.component_updater;
+
+import android.content.Intent;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.android_webview.test.AwActivityTestRule;
+import org.chromium.android_webview.test.AwJUnit4ClassRunner;
+import org.chromium.android_webview.test.util.EmbeddedComponentLoaderFactory;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.FileUtils;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.components.component_updater.EmbeddedComponentLoader;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test for {@link EmbeddedComponentLoader}. It's an integeration-like test where it uses mock
+ * native loaders and connect to {@link MockComponentProviderService}.
+ *
+ * Some test assertion are made in test/browser/embedded_component_loader_test_helper.cc
+ */
+@RunWith(AwJUnit4ClassRunner.class)
+@JNINamespace("component_updater")
+public class EmbeddedComponentLoaderTest {
+    private static CallbackHelper sOnComponentLoadedHelper = new CallbackHelper();
+    private static CallbackHelper sOnComponentLoadFailedHelper = new CallbackHelper();
+    private static List<String> sNativeErrors;
+
+    private static final String TEST_DIRECTORY_NAME = "MockComponentsProviderService_Dir";
+    private static final String TEST_COMPONENT_ID = "jebgalgnebhfojomionfpkfelancnnkf";
+    private static final String MANIFEST_JSON_STRING = "{"
+            + "\n\"manifest_version\": 2,"
+            + "\n\"name\": \"jebgalgnebhfojomionfpkfelancnnkf\","
+            + "\n\"version\": \"123.456.789\""
+            + "\n}";
+
+    // Use AwActivityTestRule to start a browser process and init native library.
+    @Rule
+    public AwActivityTestRule mActivityTestRule = new AwActivityTestRule();
+
+    @Before
+    public void setUp() throws Exception {
+        sNativeErrors = new ArrayList<>();
+        File testDirectory = getTestDirectory();
+        Assert.assertTrue(testDirectory.isDirectory() || testDirectory.mkdirs());
+    }
+
+    @After
+    public void tearDown() {
+        Assert.assertTrue("Failed to cleanup temporary test files",
+                FileUtils.recursivelyDeleteFile(getTestDirectory(), null));
+        if (!sNativeErrors.isEmpty()) {
+            StringBuilder builder = new StringBuilder();
+            builder.append("(");
+            builder.append(sNativeErrors.size());
+            builder.append(") Native errors occured:");
+            for (String error : sNativeErrors) {
+                builder.append("\n\n");
+                builder.append("Native Error: ");
+                builder.append(error);
+            }
+            Assert.fail(builder.toString());
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testLoadComponents() throws Exception {
+        int onComponentLoadedCallCount = sOnComponentLoadedHelper.getCallCount();
+        int onComponentLoadFailedCallCount = sOnComponentLoadFailedHelper.getCallCount();
+
+        File testDirectory = getTestDirectory();
+        File file = new File(testDirectory, "file.test");
+        Assert.assertTrue(file.exists() || file.createNewFile());
+        File manifestFile = new File(testDirectory, "manifest.json");
+        FileUtils.copyStreamToFile(
+                new ByteArrayInputStream(MANIFEST_JSON_STRING.getBytes()), manifestFile);
+
+        Intent intent = new Intent(
+                ContextUtils.getApplicationContext(), MockComponentsProviderService.class);
+        intent.putExtra(TEST_COMPONENT_ID,
+                new String[] {file.getAbsolutePath(), manifestFile.getAbsolutePath()});
+
+        EmbeddedComponentLoader mLoader =
+                EmbeddedComponentLoaderFactory.makeEmbeddedComponentLoader();
+        mLoader.connect(intent);
+
+        // Should be called once for AvailableComponentLoaderPolicy.
+        sOnComponentLoadedHelper.waitForCallback(
+                "Timed out waiting for onComponentLoaded() to be called",
+                onComponentLoadedCallCount, 1, AwActivityTestRule.WAIT_TIMEOUT_MS,
+                TimeUnit.MILLISECONDS);
+        // Should be called once for UnavailableComponentLoaderPolicy.
+        sOnComponentLoadFailedHelper.waitForCallback(
+                "Timed out waiting for onComponentLoadFailed() to be called",
+                onComponentLoadFailedCallCount, 1, AwActivityTestRule.WAIT_TIMEOUT_MS,
+                TimeUnit.MILLISECONDS);
+    }
+
+    @CalledByNative
+    private static void onComponentLoaded() {
+        sOnComponentLoadedHelper.notifyCalled();
+    }
+
+    @CalledByNative
+    private static void onComponentLoadFailed() {
+        sOnComponentLoadFailedHelper.notifyCalled();
+    }
+
+    @CalledByNative
+    private static void fail(String error) {
+        sNativeErrors.add(error);
+    }
+
+    private static File getTestDirectory() {
+        return new File(ContextUtils.getApplicationContext().getFilesDir(), TEST_DIRECTORY_NAME);
+    }
+}
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/component_updater/MockComponentsProviderService.java b/android_webview/javatests/src/org/chromium/android_webview/test/component_updater/MockComponentsProviderService.java
new file mode 100644
index 0000000..a2cef4b9
--- /dev/null
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/component_updater/MockComponentsProviderService.java
@@ -0,0 +1,60 @@
+// 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.
+
+package org.chromium.android_webview.test.component_updater;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+
+import org.chromium.android_webview.services.ComponentsProviderService;
+import org.chromium.components.component_updater.IComponentsProviderService;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+
+/**
+ * Mock {@link org.chrmoium.android_webview.services.ComponentsProviderService} for tests.
+ *
+ * It accepts a list of files paths in the onBind {@link Intent} that it opens as a result for the
+ * incoming componentId.
+ */
+public class MockComponentsProviderService extends Service {
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new IComponentsProviderService.Stub() {
+            @Override
+            public void getFilesForComponent(String componentId, ResultReceiver resultReceiver) {
+                CharSequence[] filePaths;
+                if ((filePaths = intent.getCharSequenceArrayExtra(componentId)) != null) {
+                    Bundle resultBundle = new Bundle();
+                    HashMap<String, ParcelFileDescriptor> resultMap = new HashMap<>();
+                    for (CharSequence filePath : filePaths) {
+                        File file = new File(filePath.toString());
+                        try {
+                            resultMap.put(file.getName(),
+                                    ParcelFileDescriptor.open(
+                                            file, ParcelFileDescriptor.MODE_READ_ONLY));
+                        } catch (FileNotFoundException exception) {
+                            throw new RuntimeException(exception);
+                        }
+                    }
+                    resultBundle.putSerializable(ComponentsProviderService.KEY_RESULT, resultMap);
+                    resultReceiver.send(ComponentsProviderService.RESULT_OK, resultBundle);
+                } else {
+                    resultReceiver.send(ComponentsProviderService.RESULT_FAILED, null);
+                }
+            }
+
+            @Override
+            public boolean onNewVersion(String componentId, String installPath, String version) {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/util/EmbeddedComponentLoaderFactory.java b/android_webview/javatests/src/org/chromium/android_webview/test/util/EmbeddedComponentLoaderFactory.java
new file mode 100644
index 0000000..63126ed
--- /dev/null
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/util/EmbeddedComponentLoaderFactory.java
@@ -0,0 +1,32 @@
+// 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.
+
+package org.chromium.android_webview.test.util;
+
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.components.component_updater.ComponentLoaderPolicyBridge;
+import org.chromium.components.component_updater.EmbeddedComponentLoader;
+
+import java.util.Arrays;
+
+/**
+ * A utility class to bridge to native to get list of native component loaders for
+ * EmbeddedComponentLoaderTest.
+ */
+@JNINamespace("component_updater")
+public class EmbeddedComponentLoaderFactory {
+    // Shouldn't instantiate this class.
+    private EmbeddedComponentLoaderFactory() {}
+
+    public static EmbeddedComponentLoader makeEmbeddedComponentLoader() {
+        return new EmbeddedComponentLoader(Arrays.asList(
+                EmbeddedComponentLoaderFactoryJni.get().getComponentLoaderPolicies()));
+    }
+
+    @NativeMethods
+    interface Natives {
+        ComponentLoaderPolicyBridge[] getComponentLoaderPolicies();
+    }
+}
\ No newline at end of file
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 91d14626..68658cd0 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -47,6 +47,7 @@
   deps = [
     ":webview_instrumentation_apk_assets",
     ":webview_instrumentation_apk_resources",
+    ":webview_instrumentation_test_mock_services_java",
     ":webview_instrumentation_test_utils_java",
     "//android_webview:android_webview_java",
     "//android_webview:android_webview_product_config_java",
@@ -181,6 +182,7 @@
   min_sdk_version = 21
 
   deps = [
+    ":webview_instrumentation_test_mock_services_java",
     ":webview_instrumentation_test_utils_java",
     "//android_webview:android_webview_java",
     "//android_webview:common_aidl_java",
@@ -194,12 +196,14 @@
     "//android_webview/test/embedded_test_server:aw_net_java_test_support",
     "//base:base_java",
     "//base:base_java_test_support",
+    "//base:jni_java",
     "//components/autofill/android:autofill_java",
     "//components/autofill/android/provider:java",
     "//components/autofill/android/provider/test_support:component_autofill_provider_java_test_support",
     "//components/autofill/core/common/mojom:mojo_types_java",
     "//components/background_task_scheduler:background_task_scheduler_task_ids_java",
     "//components/component_updater/android:component_provider_service_aidl_java",
+    "//components/component_updater/android:embedded_component_loader_java",
     "//components/content_capture/android:java",
     "//components/embedder_support/android:util_java",
     "//components/embedder_support/android:web_contents_delegate_java",
@@ -345,6 +349,7 @@
     "../javatests/src/org/chromium/android_webview/test/common/crash/CrashInfoEqualityMatcher.java",
     "../javatests/src/org/chromium/android_webview/test/common/crash/CrashInfoTest.java",
     "../javatests/src/org/chromium/android_webview/test/common/variations/VariationsUtilsTest.java",
+    "../javatests/src/org/chromium/android_webview/test/component_updater/EmbeddedComponentLoaderTest.java",
     "../javatests/src/org/chromium/android_webview/test/devui/AwNonembeddedUmaRecorderTest.java",
     "../javatests/src/org/chromium/android_webview/test/devui/CrashesListFragmentTest.java",
     "../javatests/src/org/chromium/android_webview/test/devui/DeveloperUiTest.java",
@@ -361,8 +366,6 @@
     "../javatests/src/org/chromium/android_webview/test/services/MetricsBridgeServiceTest.java",
     "../javatests/src/org/chromium/android_webview/test/services/MetricsBridgeServiceUnitTest.java",
     "../javatests/src/org/chromium/android_webview/test/services/MinidumpUploadJobTest.java",
-    "../javatests/src/org/chromium/android_webview/test/services/MockMetricsBridgeService.java",
-    "../javatests/src/org/chromium/android_webview/test/services/MockVariationsSeedServer.java",
     "../javatests/src/org/chromium/android_webview/test/services/ServiceConnectionHelper.java",
     "../javatests/src/org/chromium/android_webview/test/services/VariationsSeedServerTest.java",
     "../javatests/src/org/chromium/android_webview/test/services/VisualStateCallbackTest.java",
@@ -405,9 +408,27 @@
   ]
 }
 
+android_library("webview_instrumentation_test_mock_services_java") {
+  testonly = true
+  sources = [
+    "../javatests/src/org/chromium/android_webview/test/component_updater/MockComponentsProviderService.java",
+    "../javatests/src/org/chromium/android_webview/test/services/MockMetricsBridgeService.java",
+    "../javatests/src/org/chromium/android_webview/test/services/MockVariationsSeedServer.java",
+  ]
+  deps = [
+    "//android_webview:common_aidl_java",
+    "//android_webview/nonembedded:services_java",
+    "//base:base_java",
+    "//base:base_java_test_support",
+    "//components/component_updater/android:component_provider_service_aidl_java",
+    "//third_party/junit",
+  ]
+}
+
 android_library("webview_instrumentation_test_utils_java") {
   testonly = true
   sources = [
+    "../javatests/src/org/chromium/android_webview/test/util/EmbeddedComponentLoaderFactory.java",
     "../javatests/src/org/chromium/android_webview/test/util/RendererProcessMetricsProviderUtils.java",
     "../javatests/src/org/chromium/android_webview/test/util/VariationsTestUtils.java",
     "shell/src/org/chromium/android_webview/test/util/MemoryMetricsLoggerUtils.java",
@@ -416,6 +437,7 @@
     "//android_webview:common_variations_java",
     "//base:base_java",
     "//base:jni_java",
+    "//components/component_updater/android:embedded_component_loader_java",
     "//components/variations/android:variations_java",
     "//third_party/junit",
   ]
@@ -425,6 +447,8 @@
 generate_jni("webview_instrumentation_test_native_jni") {
   testonly = true
   sources = [
+    "../javatests/src/org/chromium/android_webview/test/component_updater/EmbeddedComponentLoaderTest.java",
+    "../javatests/src/org/chromium/android_webview/test/util/EmbeddedComponentLoaderFactory.java",
     "../javatests/src/org/chromium/android_webview/test/util/RendererProcessMetricsProviderUtils.java",
     "../javatests/src/org/chromium/android_webview/test/util/VariationsTestUtils.java",
     "shell/src/org/chromium/android_webview/test/util/MemoryMetricsLoggerUtils.java",
@@ -433,6 +457,7 @@
 
 source_set("webview_instrumentation_test_native_jni_impl") {
   sources = [
+    "browser/embedded_component_loader_test_helper.cc",
     "browser/renderer_process_metrics_provider_utils.cc",
     "browser/variations_test_utils.cc",
     "shell/memory_metrics_logger_utils.cc",
@@ -444,6 +469,8 @@
     "//android_webview/browser/metrics",
     "//base",
     "//base/test:test_support",
+    "//components/component_updater/android:embedded_component_loader",
+    "//components/component_updater/android:embedded_component_loader_jni_headers",
     "//components/embedder_support/android/metrics",
     "//third_party/protobuf:protobuf_lite",
   ]
diff --git a/android_webview/test/browser/DEPS b/android_webview/test/browser/DEPS
new file mode 100644
index 0000000..f236ef95
--- /dev/null
+++ b/android_webview/test/browser/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/component_updater/android",
+]
\ No newline at end of file
diff --git a/android_webview/test/browser/embedded_component_loader_test_helper.cc b/android_webview/test/browser/embedded_component_loader_test_helper.cc
new file mode 100644
index 0000000..64503e6f
--- /dev/null
+++ b/android_webview/test/browser/embedded_component_loader_test_helper.cc
@@ -0,0 +1,114 @@
+// 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 <stdint.h>
+
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "android_webview/test/webview_instrumentation_test_native_jni/EmbeddedComponentLoaderFactory_jni.h"
+#include "android_webview/test/webview_instrumentation_test_native_jni/EmbeddedComponentLoaderTest_jni.h"
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/containers/flat_map.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "components/component_updater/android/component_loader_policy.h"
+
+namespace component_updater {
+
+namespace {
+// This hash corresponds to "jebgalgnebhfojomionfpkfelancnnkf".
+constexpr uint8_t kAvailableSha256Hash[] = {
+    0x94, 0x16, 0x0b, 0x6d, 0x41, 0x75, 0xe9, 0xec, 0x8e, 0xd5, 0xfa,
+    0x54, 0xb0, 0xd2, 0xdd, 0xa5, 0x6e, 0x05, 0x6b, 0xe8, 0x73, 0x47,
+    0xf6, 0xc4, 0x11, 0x9f, 0xbc, 0xb3, 0x09, 0xb3, 0x5b, 0x40};
+
+// This hash corresponds to "abcdefjhijk".
+constexpr uint8_t kUnavailableComponentSha256Hash[] = {
+    0x6a, 0xcc, 0xdf, 0xdb, 0x7b, 0xa0, 0xe9, 0x61, 0x14, 0x94, 0x27,
+    0x29, 0xe0, 0x11, 0xaa, 0x24, 0xe8, 0x58, 0xe9, 0x9f, 0x78, 0x03,
+    0x13, 0x40, 0x95, 0x2e, 0x65, 0xc3, 0x9c, 0x68, 0xa9, 0xcc};
+
+// Check that `condition` is `true` otherwise send an error message to java that
+// will trigger a failure at the end of the java test with the given `error`
+// message.
+static void ExpectTrueToJava(bool condition, const std::string& error) {
+  if (!condition) {
+    JNIEnv* env = base::android::AttachCurrentThread();
+    Java_EmbeddedComponentLoaderTest_fail(
+        env, base::android::ConvertUTF8ToJavaString(env, error));
+  }
+}
+
+class AvailableComponentLoaderPolicy : public ComponentLoaderPolicy {
+ public:
+  AvailableComponentLoaderPolicy() = default;
+  ~AvailableComponentLoaderPolicy() override = default;
+
+  void ComponentLoaded(
+      const base::Version& version,
+      const base::flat_map<std::string, int>& fd_map,
+      std::unique_ptr<base::DictionaryValue> manifest) override {
+    // Make sure these values match the values in the
+    // EmbeddedComponentLoaderTest.
+    ExpectTrueToJava(version.GetString() == "123.456.789",
+                     "version != 123.456.789");
+    ExpectTrueToJava(fd_map.size() == 1u, "fd_map.size != 1");
+    ExpectTrueToJava(fd_map.find("file.test") != fd_map.end(),
+                     "file.test is not found in the fd_map");
+    Java_EmbeddedComponentLoaderTest_onComponentLoaded(
+        base::android::AttachCurrentThread());
+  }
+
+  void ComponentLoadFailed() override {
+    ExpectTrueToJava(
+        false, "AvailableComponentLoaderPolicy#ComponentLoadFailed is called");
+  }
+
+  void GetHash(std::vector<uint8_t>* hash) const override {
+    hash->assign(std::begin(kAvailableSha256Hash),
+                 std::end(kAvailableSha256Hash));
+  }
+};
+
+class UnavailableComponentLoaderPolicy : public ComponentLoaderPolicy {
+ public:
+  UnavailableComponentLoaderPolicy() = default;
+  ~UnavailableComponentLoaderPolicy() override = default;
+
+  void ComponentLoaded(
+      const base::Version& version,
+      const base::flat_map<std::string, int>& fd_map,
+      std::unique_ptr<base::DictionaryValue> manifest) override {
+    ExpectTrueToJava(
+        false, "UnavailableComponentLoaderPolicy#ComponentLoaded is called");
+  }
+
+  void ComponentLoadFailed() override {
+    Java_EmbeddedComponentLoaderTest_onComponentLoadFailed(
+        base::android::AttachCurrentThread());
+  }
+
+  void GetHash(std::vector<uint8_t>* hash) const override {
+    hash->assign(std::begin(kUnavailableComponentSha256Hash),
+                 std::end(kUnavailableComponentSha256Hash));
+  }
+};
+
+}  // namespace
+
+static base::android::ScopedJavaLocalRef<jobjectArray>
+JNI_EmbeddedComponentLoaderFactory_GetComponentLoaderPolicies(JNIEnv* env) {
+  ComponentLoaderPolicyVector loaders;
+  loaders.push_back(std::make_unique<AvailableComponentLoaderPolicy>());
+  loaders.push_back(std::make_unique<UnavailableComponentLoaderPolicy>());
+  return AndroidComponentLoaderPolicy::
+      ToJavaArrayOfAndroidComponentLoaderPolicy(env, std::move(loaders));
+}
+
+}  // namespace component_updater
diff --git a/android_webview/test/shell/AndroidManifest.xml b/android_webview/test/shell/AndroidManifest.xml
index 9129bafc..fb436e5 100644
--- a/android_webview/test/shell/AndroidManifest.xml
+++ b/android_webview/test/shell/AndroidManifest.xml
@@ -87,6 +87,8 @@
     <service android:name="org.chromium.android_webview.services.ComponentsProviderService"
              android:exported="true"
              android:process=":webview_service" />
+    <service android:name="org.chromium.android_webview.test.component_updater.MockComponentsProviderService"
+             android:process=":webview_service"/>
     <!-- Components for Developer UI, make sure that any changes in these components reflect
          the corresponding original components in nonembedded/java/AndroidManifest.xml -->
     <activity android:name="org.chromium.android_webview.devui.MainActivity"
diff --git a/android_webview/tools/run_cts.pydeps b/android_webview/tools/run_cts.pydeps
index e8d1698..82b09e19 100644
--- a/android_webview/tools/run_cts.pydeps
+++ b/android_webview/tools/run_cts.pydeps
@@ -75,4 +75,3 @@
 //third_party/catapult/devil/devil/utils/timeout_retry.py
 //third_party/catapult/devil/devil/utils/watchdog_timer.py
 //third_party/catapult/devil/devil/utils/zip_utils.py
-//third_party/catapult/third_party/six/six.py
diff --git a/ash/accessibility/point_scan_controller.cc b/ash/accessibility/point_scan_controller.cc
index 44978ec..7cf15996 100644
--- a/ash/accessibility/point_scan_controller.cc
+++ b/ash/accessibility/point_scan_controller.cc
@@ -42,8 +42,11 @@
   horizontal_range_layer_.reset(
       new PointScanLayer(this, PointScanLayer::Orientation::HORIZONTAL,
                          PointScanLayer::Type::RANGE));
+  gfx::Rect layer_bounds = horizontal_range_layer_->bounds();
+  horizontal_range_layer_info_.offset = layer_bounds.x();
+  horizontal_range_layer_info_.offset_start = layer_bounds.x();
   horizontal_range_layer_info_.offset_bound =
-      horizontal_range_layer_->bounds().width() - kDefaultRangeWidthDips;
+      layer_bounds.right() - kDefaultRangeWidthDips;
   horizontal_range_layer_->Start();
 }
 
@@ -68,8 +71,11 @@
   vertical_range_layer_.reset(
       new PointScanLayer(this, PointScanLayer::Orientation::VERTICAL,
                          PointScanLayer::Type::RANGE));
+  gfx::Rect layer_bounds = vertical_range_layer_->bounds();
+  vertical_range_layer_info_.offset = layer_bounds.y();
+  vertical_range_layer_info_.offset = layer_bounds.y();
   vertical_range_layer_info_.offset_bound =
-      vertical_range_layer_->bounds().height() - kDefaultRangeHeightDips;
+      layer_bounds.bottom() - kDefaultRangeHeightDips;
   vertical_range_layer_->Start();
 }
 
diff --git a/ash/accessibility/point_scan_layer.cc b/ash/accessibility/point_scan_layer.cc
index 8f9ecca..79aed5702 100644
--- a/ash/accessibility/point_scan_layer.cc
+++ b/ash/accessibility/point_scan_layer.cc
@@ -7,6 +7,7 @@
 #include "ash/shell.h"
 #include "third_party/skia/include/core/SkPaint.h"
 #include "third_party/skia/include/core/SkPath.h"
+#include "third_party/skia/include/effects/SkDashPathEffect.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/paint_recorder.h"
@@ -19,10 +20,14 @@
 namespace ash {
 
 namespace {
-const int kDefaultStrokeWidth = 6;
+constexpr int kStrokeWidth = 2;
 constexpr int kDefaultRangeWidthDips = 150;
 constexpr int kDefaultRangeHeightDips = 120;
-constexpr int kDefaultPaddingDips = 4;
+constexpr int kDashLengthDips = 6;
+constexpr int kGapLengthDips = 3;
+
+constexpr SkColor kInnerColor = gfx::kGoogleBlue300;
+constexpr SkColor kOuterColor = gfx::kGoogleBlue600;
 
 display::Display GetPrimaryDisplay() {
   DCHECK(display::Screen::GetScreen());
@@ -43,24 +48,20 @@
 }
 
 void PointScanLayer::Start() {
-  gfx::Point start;
+  gfx::Point start = bounds().origin();
   gfx::Point end;
+  int padding = kStrokeWidth / 2 + 1;
 
-  // Set the end point, based on the orientation.
-  if (orientation_ == PointScanLayer::Orientation::HORIZONTAL)
+  // Set the end point, based on the orientation. Offset by padding so lines
+  // draw onscreen.
+  if (orientation_ == PointScanLayer::Orientation::HORIZONTAL) {
     end = bounds().bottom_left();
-  else if (orientation_ == PointScanLayer::Orientation::VERTICAL)
+    start.Offset(padding, 0);
+    end.Offset(padding, 0);
+  } else if (orientation_ == PointScanLayer::Orientation::VERTICAL) {
     end = bounds().top_right();
-
-  // Ranges need to offset |line_| by the range width.
-  if (type_ == PointScanLayer::Type::RANGE) {
-    if (orientation_ == PointScanLayer::Orientation::HORIZONTAL) {
-      start.Offset(kDefaultPaddingDips, 0);
-      end.Offset(kDefaultPaddingDips, 0);
-    } else if (orientation_ == PointScanLayer::Orientation::VERTICAL) {
-      start.Offset(0, kDefaultPaddingDips);
-      end.Offset(0, kDefaultPaddingDips);
-    }
+    start.Offset(0, padding);
+    end.Offset(0, padding);
   }
 
   line_.start = start;
@@ -70,6 +71,7 @@
 
 void PointScanLayer::Pause() {
   is_moving_ = false;
+  layer()->SchedulePaint(bounds());
 }
 
 bool PointScanLayer::IsMoving() const {
@@ -91,24 +93,60 @@
   cc::PaintFlags flags;
   flags.setAntiAlias(true);
   flags.setStyle(cc::PaintFlags::kStroke_Style);
-  flags.setStrokeWidth(kDefaultStrokeWidth);
-  flags.setColor(gfx::kGoogleBlue300);
+  flags.setStrokeWidth(kStrokeWidth);
+  int half_stroke_width = kStrokeWidth / 2;
 
-  SkPath path;
-
-  if (type_ == PointScanLayer::Type::RANGE) {
-    if (orientation_ == PointScanLayer::Orientation::HORIZONTAL) {
-      path.moveTo(line_.start.x() + kDefaultRangeWidthDips, line_.start.y());
-      path.lineTo(line_.end.x() + kDefaultRangeWidthDips, line_.end.y());
-    } else if (orientation_ == PointScanLayer::Orientation::VERTICAL) {
-      path.moveTo(line_.start.x(), line_.start.y() + kDefaultRangeHeightDips);
-      path.lineTo(line_.end.x(), line_.end.y() + kDefaultRangeHeightDips);
-    }
+  if (is_moving_) {
+    SkScalar intervals[] = {kDashLengthDips, kGapLengthDips};
+    int intervals_length = 2;
+    flags.setPathEffect(SkDashPathEffect::Make(intervals, intervals_length, 0));
   }
 
-  path.moveTo(line_.start.x(), line_.start.y());
-  path.lineTo(line_.end.x(), line_.end.y());
-  recorder.canvas()->DrawPath(path, flags);
+  flags.setColor(kOuterColor);
+  if (orientation_ == PointScanLayer::Orientation::HORIZONTAL) {
+    DrawLineWithOffsets(recorder.canvas(), flags, -half_stroke_width, 0);
+  } else if (orientation_ == PointScanLayer::Orientation::VERTICAL) {
+    DrawLineWithOffsets(recorder.canvas(), flags, 0, -half_stroke_width);
+  }
+
+  flags.setColor(kInnerColor);
+  if (orientation_ == PointScanLayer::Orientation::HORIZONTAL) {
+    DrawLineWithOffsets(recorder.canvas(), flags, half_stroke_width, 0);
+  } else if (orientation_ == PointScanLayer::Orientation::VERTICAL) {
+    DrawLineWithOffsets(recorder.canvas(), flags, 0, half_stroke_width);
+  }
+
+  if (type_ != PointScanLayer::Type::RANGE)
+    return;
+
+  // Draw the second line for range scanning.
+  flags.setColor(kOuterColor);
+  if (orientation_ == PointScanLayer::Orientation::HORIZONTAL) {
+    DrawLineWithOffsets(recorder.canvas(), flags,
+                        kDefaultRangeWidthDips - half_stroke_width, 0);
+  } else if (orientation_ == PointScanLayer::Orientation::VERTICAL) {
+    DrawLineWithOffsets(recorder.canvas(), flags, 0,
+                        kDefaultRangeHeightDips - half_stroke_width);
+  }
+
+  flags.setColor(kInnerColor);
+  if (orientation_ == PointScanLayer::Orientation::HORIZONTAL) {
+    DrawLineWithOffsets(recorder.canvas(), flags,
+                        kDefaultRangeWidthDips + half_stroke_width, 0);
+  } else if (orientation_ == PointScanLayer::Orientation::VERTICAL) {
+    DrawLineWithOffsets(recorder.canvas(), flags, 0,
+                        kDefaultRangeHeightDips + half_stroke_width);
+  }
+}
+
+void PointScanLayer::DrawLineWithOffsets(gfx::Canvas* canvas,
+                                         cc::PaintFlags flags,
+                                         int x_offset,
+                                         int y_offset) {
+  SkPath path;
+  path.moveTo(line_.start.x() + x_offset, line_.start.y() + y_offset);
+  path.lineTo(line_.end.x() + x_offset, line_.end.y() + y_offset);
+  canvas->DrawPath(path, flags);
 }
 
 }  // namespace ash
diff --git a/ash/accessibility/point_scan_layer.h b/ash/accessibility/point_scan_layer.h
index 971b617..cbef553 100644
--- a/ash/accessibility/point_scan_layer.h
+++ b/ash/accessibility/point_scan_layer.h
@@ -10,6 +10,10 @@
 #include "ash/ash_export.h"
 #include "ui/compositor/layer.h"
 
+namespace gfx {
+class Canvas;
+}
+
 namespace ash {
 
 class PointScanLayer : public AccessibilityLayer {
@@ -44,6 +48,11 @@
   int GetInset() const override;
 
  private:
+  void DrawLineWithOffsets(gfx::Canvas* canvas,
+                           cc::PaintFlags flags,
+                           int x_offset,
+                           int y_offset);
+
   // ui:LayerDelegate overrides:
   void OnPaintLayer(const ui::PaintContext& context) override;
   void OnLayerChange(PointScanLayerAnimationInfo* animation_info);
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index 69c78e5..7d15ef4d 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -1132,10 +1132,6 @@
   ResetHomeLauncherIfShown();
 }
 
-void AppListControllerImpl::LogSearchAbandonHistogram() {
-  RecordSearchAbandonWithQueryLengthHistogram(GetLastQueryLength());
-}
-
 void AppListControllerImpl::InvokeSearchResultAction(
     const std::string& result_id,
     int action_index) {
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h
index 4492964..8b7ff8c 100644
--- a/ash/app_list/app_list_controller_impl.h
+++ b/ash/app_list/app_list_controller_impl.h
@@ -176,7 +176,6 @@
                         AppListLaunchType launch_type,
                         int suggestion_index,
                         bool launch_as_default) override;
-  void LogSearchAbandonHistogram() override;
   void InvokeSearchResultAction(const std::string& result_id,
                                 int action_index) override;
   using GetContextMenuModelCallback =
diff --git a/ash/app_list/app_list_metrics.cc b/ash/app_list/app_list_metrics.cc
index 56fd308..6b6e308 100644
--- a/ash/app_list/app_list_metrics.cc
+++ b/ash/app_list/app_list_metrics.cc
@@ -16,14 +16,6 @@
 #include "ui/compositor/compositor.h"
 
 namespace ash {
-namespace {
-
-// This constant affects logging, and should not be changed without
-// deprecating this UMA histogram:
-//  - Apps.AppListSearchAbandonQueryLength
-constexpr int kMaxLoggedQueryLength = 10;
-
-}  // namespace
 
 // The UMA histogram that logs smoothness of pagination animation.
 constexpr char kPaginationTransitionAnimationSmoothness[] =
@@ -55,12 +47,6 @@
 constexpr char kAppListZeroStateSearchResultRemovalHistogram[] =
     "Apps.AppList.ZeroStateSearchResultRemovalDecision";
 
-// The UMA histogram that logs the length of the query when user abandons
-// results of a queried search or recommendations of zero state(zero length
-// query) in launcher UI.
-constexpr char kSearchAbandonQueryLengthHistogram[] =
-    "Apps.AppListSearchAbandonQueryLength";
-
 // The base UMA histogram that logs app launches within the AppList and shelf.
 constexpr char kAppListAppLaunched[] = "Apps.AppListAppLaunchedV2";
 
@@ -154,12 +140,6 @@
       ApplistSearchResultOpenedSource::kMaxApplistSearchResultOpenedSource);
 }
 
-void RecordSearchAbandonWithQueryLengthHistogram(int query_length) {
-  UMA_HISTOGRAM_EXACT_LINEAR(kSearchAbandonQueryLengthHistogram,
-                             std::min(query_length, kMaxLoggedQueryLength),
-                             kMaxLoggedQueryLength);
-}
-
 void RecordZeroStateSearchResultUserActionHistogram(
     ZeroStateSearchResultUserActionType action) {
   UMA_HISTOGRAM_ENUMERATION(kAppListZeroStateSearchResultUserActionHistogram,
diff --git a/ash/app_list/app_list_metrics.h b/ash/app_list/app_list_metrics.h
index 6b99acf..24959f3 100644
--- a/ash/app_list/app_list_metrics.h
+++ b/ash/app_list/app_list_metrics.h
@@ -295,9 +295,6 @@
 void RecordZeroStateSearchResultRemovalHistogram(
     ZeroStateSearchResutRemovalConfirmation removal_decision);
 
-APP_LIST_EXPORT void RecordSearchAbandonWithQueryLengthHistogram(
-    int query_length);
-
 APP_LIST_EXPORT void RecordSearchResultOpenSource(
     const SearchResult* result,
     const AppListModel* model,
diff --git a/ash/app_list/app_list_presenter_delegate_impl.cc b/ash/app_list/app_list_presenter_delegate_impl.cc
index 1710b0d8..99d7133 100644
--- a/ash/app_list/app_list_presenter_delegate_impl.cc
+++ b/ash/app_list/app_list_presenter_delegate_impl.cc
@@ -4,6 +4,9 @@
 
 #include "ash/app_list/app_list_presenter_delegate_impl.h"
 
+#include <memory>
+#include <utility>
+
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/app_list/app_list_presenter_impl.h"
 #include "ash/app_list/app_list_util.h"
@@ -279,11 +282,22 @@
 
     // Don't dismiss the auto-hide shelf if event happened in status area. Then
     // the event can still be propagated.
-    base::Optional<Shelf::ScopedAutoHideLock> auto_hide_lock;
     const aura::Window* status_window =
         shelf->shelf_widget()->status_area_widget()->GetNativeWindow();
-    if (status_window && status_window->Contains(target))
-      auto_hide_lock.emplace(shelf);
+    if (status_window && status_window->Contains(target)) {
+      auto shelf_visibility_lock =
+          std::make_unique<ShelfLayoutManager::ScopedVisibilityLock>(
+              shelf->shelf_layout_manager());
+
+      // Use a task runner to delete the |shelf_visibility_lock| and update the
+      // shelf visibility after the current event has been handled by the shelf.
+      // This is important for the case where dismissing the app list might hide
+      // the shelf, which would stop the shelf from handling the event.
+      // TODO(crbug.com/1186479): Investigate whether there is a better way to
+      // do this, instead of using a task runner here.
+      base::ThreadTaskRunnerHandle::Get()->DeleteSoon(
+          FROM_HERE, std::move(shelf_visibility_lock));
+    }
 
     // Record the current AppListViewState to be used later for metrics. The
     // AppListViewState will change on app launch, so this will record the
diff --git a/ash/app_list/app_list_presenter_delegate_unittest.cc b/ash/app_list/app_list_presenter_delegate_unittest.cc
index 0463ea08..1ffd3fd 100644
--- a/ash/app_list/app_list_presenter_delegate_unittest.cc
+++ b/ash/app_list/app_list_presenter_delegate_unittest.cc
@@ -2812,6 +2812,7 @@
   ui::test::EventGenerator* generator = GetEventGenerator();
   generator->GestureTapAt(
       GetPrimaryUnifiedSystemTray()->GetBoundsInScreen().CenterPoint());
+  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(GetPrimaryUnifiedSystemTray()->IsBubbleShown());
   GetAppListTestHelper()->CheckVisibility(false);
   EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
diff --git a/ash/app_list/app_list_view_delegate.h b/ash/app_list/app_list_view_delegate.h
index 5bf684f..b515f294 100644
--- a/ash/app_list/app_list_view_delegate.h
+++ b/ash/app_list/app_list_view_delegate.h
@@ -75,9 +75,6 @@
                                 int suggestion_index,
                                 bool launch_as_default) = 0;
 
-  // Logs the UMA histogram metrics for user's abandonment of launcher search.
-  virtual void LogSearchAbandonHistogram() = 0;
-
   // Called to invoke a custom action on a result with |result_id|.
   // |action_index| corresponds to the index of an icon in
   // |result.action_icons()|.
diff --git a/ash/app_list/test/app_list_test_view_delegate.h b/ash/app_list/test/app_list_test_view_delegate.h
index 406e83c6..7e5826c 100644
--- a/ash/app_list/test/app_list_test_view_delegate.h
+++ b/ash/app_list/test/app_list_test_view_delegate.h
@@ -69,7 +69,6 @@
                         ash::AppListLaunchType launch_type,
                         int suggestion_index,
                         bool launch_as_default) override;
-  void LogSearchAbandonHistogram() override {}
   void InvokeSearchResultAction(const std::string& result_id,
                                 int action_index) override {}
   void GetSearchResultContextMenuModel(
diff --git a/ash/app_list/views/search_box_view.cc b/ash/app_list/views/search_box_view.cc
index dd6fc21..aa15c5f4 100644
--- a/ash/app_list/views/search_box_view.cc
+++ b/ash/app_list/views/search_box_view.cc
@@ -234,7 +234,6 @@
   views::ImageButton* close = close_button();
   close->SetCallback(base::BindRepeating(
       [](SearchBoxView* view) {
-        view->view_delegate_->LogSearchAbandonHistogram();
         view->SetSearchBoxActive(false, ui::ET_UNKNOWN);
         view->ClearSearch();
       },
@@ -591,8 +590,6 @@
   if (!is_search_box_active())
     return;
 
-  view_delegate_->LogSearchAbandonHistogram();
-
   contents_view_->search_results_page_view()
       ->result_selection_controller()
       ->ClearSelection();
diff --git a/ash/clipboard/clipboard_history_controller_impl.cc b/ash/clipboard/clipboard_history_controller_impl.cc
index efd02c4..bcd484a 100644
--- a/ash/clipboard/clipboard_history_controller_impl.cc
+++ b/ash/clipboard/clipboard_history_controller_impl.cc
@@ -358,7 +358,7 @@
         break;
       }
     }
-    item_value.SetKey("idToken", base::Value(item.id().ToString()));
+    item_value.SetKey("id", base::Value(item.id().ToString()));
     item_results.Append(std::move(item_value));
   }
 
diff --git a/ash/components/account_manager/BUILD.gn b/ash/components/account_manager/BUILD.gn
index d60eec2..9b131d98 100644
--- a/ash/components/account_manager/BUILD.gn
+++ b/ash/components/account_manager/BUILD.gn
@@ -9,6 +9,8 @@
 
 component("account_manager") {
   sources = [
+    "access_token_fetcher.cc",
+    "access_token_fetcher.h",
     "account_manager.cc",
     "account_manager.h",
     "account_manager_ash.cc",
@@ -50,6 +52,7 @@
     "//base/test:test_support",
     "//chromeos/crosapi/mojom",
     "//components/prefs:test_support",
+    "//google_apis",
     "//net",
     "//services/network:test_support",
     "//services/network/public/cpp:cpp",
diff --git a/ash/components/account_manager/access_token_fetcher.cc b/ash/components/account_manager/access_token_fetcher.cc
new file mode 100644
index 0000000..0859b2e
--- /dev/null
+++ b/ash/components/account_manager/access_token_fetcher.cc
@@ -0,0 +1,104 @@
+// 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/components/account_manager/access_token_fetcher.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback_forward.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "chromeos/crosapi/mojom/account_manager.mojom.h"
+#include "components/account_manager_core/account_manager_util.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+
+namespace crosapi {
+
+AccessTokenFetcher::AccessTokenFetcher(
+    ash::AccountManager* account_manager,
+    mojom::AccountKeyPtr mojo_account_key,
+    base::OnceCallback<void(AccessTokenFetcher*)> done_callback,
+    mojo::PendingReceiver<mojom::AccessTokenFetcher> receiver)
+    : done_callback_(std::move(done_callback)),
+      receiver_(this, std::move(receiver)) {
+  receiver_.set_disconnect_handler(base::BindOnce(
+      &AccessTokenFetcher::OnMojoPipeError, base::Unretained(this)));
+
+  base::Optional<account_manager::AccountKey> maybe_account_key =
+      account_manager::FromMojoAccountKey(mojo_account_key);
+  if (maybe_account_key.has_value() && maybe_account_key.value().IsValid()) {
+    access_token_fetcher_ = account_manager->CreateAccessTokenFetcher(
+        /*account_key=*/maybe_account_key.value(), /*consumer=*/this);
+  }
+  // else: access_token_fetcher_ will be `nullptr`. `Start` will handle this
+  // case.
+}
+
+AccessTokenFetcher::~AccessTokenFetcher() = default;
+
+void AccessTokenFetcher::Start(const std::vector<std::string>& scopes,
+                               StartCallback callback) {
+  DCHECK(callback_.is_null());
+  DCHECK(callback);
+  callback_ = std::move(callback);
+
+  if (!access_token_fetcher_) {
+    // `access_token_fetcher_` can be null only if `account_key` is invalid /
+    // unknown.
+    OnGetTokenFailure(GoogleServiceAuthError(
+        GoogleServiceAuthError::State::USER_NOT_SIGNED_UP));
+    return;
+  }
+
+  access_token_fetcher_->Start(
+      GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
+      GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), scopes);
+}
+
+void AccessTokenFetcher::OnGetTokenSuccess(
+    const TokenResponse& token_response) {
+  mojom::AccessTokenResultPtr result =
+      mojom::AccessTokenResult::NewAccessTokenInfo(mojom::AccessTokenInfo::New(
+          token_response.access_token, token_response.expiration_time,
+          token_response.id_token));
+  std::move(callback_).Run(std::move(result));
+  Finish();
+}
+
+void AccessTokenFetcher::OnGetTokenFailure(
+    const GoogleServiceAuthError& error) {
+  mojom::AccessTokenResultPtr result = mojom::AccessTokenResult::NewError(
+      account_manager::ToMojoGoogleServiceAuthError(error));
+  std::move(callback_).Run(std::move(result));
+  Finish();
+}
+
+void AccessTokenFetcher::OnMojoPipeError() {
+  if (access_token_fetcher_)
+    access_token_fetcher_->CancelRequest();
+
+  if (callback_) {
+    // We don't need to respond to callback. The Mojo pipe has been
+    // disconnected.
+    callback_.Reset();
+  }
+
+  Finish();
+}
+
+void AccessTokenFetcher::Finish() {
+  DCHECK(callback_.is_null())
+      << "Finish called before responding to pending request";
+
+  // We cannot call `TriggerDeletion` directly because it will immediately start
+  // deleting `this`, before this method has had a chance to return.
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(done_callback_), this));
+}
+
+}  // namespace crosapi
diff --git a/ash/components/account_manager/access_token_fetcher.h b/ash/components/account_manager/access_token_fetcher.h
new file mode 100644
index 0000000..50b3acb4
--- /dev/null
+++ b/ash/components/account_manager/access_token_fetcher.h
@@ -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.
+
+#ifndef ASH_COMPONENTS_ACCOUNT_MANAGER_ACCESS_TOKEN_FETCHER_H_
+#define ASH_COMPONENTS_ACCOUNT_MANAGER_ACCESS_TOKEN_FETCHER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "ash/components/account_manager/account_manager.h"
+#include "base/callback_forward.h"
+#include "chromeos/crosapi/mojom/account_manager.mojom.h"
+#include "google_apis/gaia/oauth2_access_token_consumer.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+namespace crosapi {
+
+// Mojo interface implementation to fetch access tokens using Chrome OS Account
+// Manager.
+class COMPONENT_EXPORT(ASH_COMPONENTS_ACCOUNT_MANAGER) AccessTokenFetcher
+    : public mojom::AccessTokenFetcher,
+      public OAuth2AccessTokenConsumer {
+ public:
+  // `account_manager` is a non owning pointer to Chrome OS Account Manager and
+  // is guaranteed to outlive `this` instance.
+  // `mojo_account_key` is the account for which an access token needs to be
+  // fetched.
+  // `done_callback` is called after an access token fetch is complete. Used by
+  // the owner of `this` object to figure out when it is safe to delete it.
+  AccessTokenFetcher(
+      ash::AccountManager* account_manager,
+      mojom::AccountKeyPtr mojo_account_key,
+      base::OnceCallback<void(AccessTokenFetcher*)> done_callback,
+      mojo::PendingReceiver<mojom::AccessTokenFetcher> receiver);
+  AccessTokenFetcher(const AccessTokenFetcher&) = delete;
+  AccessTokenFetcher& operator=(const AccessTokenFetcher&) = delete;
+  ~AccessTokenFetcher() override;
+
+  // mojom::AccessTokenFetcher overrides.
+  void Start(const std::vector<std::string>& scopes,
+             StartCallback callback) override;
+
+  // OAuth2AccessTokenConsumer overrides.
+  void OnGetTokenSuccess(const TokenResponse& token_response) override;
+  void OnGetTokenFailure(const GoogleServiceAuthError& error) override;
+
+ private:
+  // Mojo pipe disconnection handler.
+  void OnMojoPipeError();
+  // Finish and cleanup by calling `done_callback_`.
+  void Finish();
+
+  std::unique_ptr<OAuth2AccessTokenFetcher> access_token_fetcher_;
+  base::OnceCallback<void(mojom::AccessTokenResultPtr)> callback_;
+  // Called after `this` object's work is done and it can be safely deleted.
+  base::OnceCallback<void(AccessTokenFetcher*)> done_callback_;
+  mojo::Receiver<mojom::AccessTokenFetcher> receiver_;
+};
+
+}  // namespace crosapi
+
+#endif  // ASH_COMPONENTS_ACCOUNT_MANAGER_ACCESS_TOKEN_FETCHER_H_
diff --git a/ash/components/account_manager/account_manager.h b/ash/components/account_manager/account_manager.h
index f03678f5..d9f4f00e 100644
--- a/ash/components/account_manager/account_manager.h
+++ b/ash/components/account_manager/account_manager.h
@@ -209,7 +209,8 @@
   // Creates and returns an |OAuth2AccessTokenFetcher| using the refresh token
   // stored for |account_key|. |IsTokenAvailable| should be |true| for
   // |account_key|, otherwise a |nullptr| is returned.
-  std::unique_ptr<OAuth2AccessTokenFetcher> CreateAccessTokenFetcher(
+  // virtual for testing.
+  virtual std::unique_ptr<OAuth2AccessTokenFetcher> CreateAccessTokenFetcher(
       const ::account_manager::AccountKey& account_key,
       OAuth2AccessTokenConsumer* consumer) const;
 
diff --git a/ash/components/account_manager/account_manager_ash.cc b/ash/components/account_manager/account_manager_ash.cc
index 9f01360..e15b625 100644
--- a/ash/components/account_manager/account_manager_ash.cc
+++ b/ash/components/account_manager/account_manager_ash.cc
@@ -4,22 +4,34 @@
 
 #include "ash/components/account_manager/account_manager_ash.h"
 
+#include <algorithm>
+#include <memory>
 #include <utility>
 
+#include "ash/components/account_manager/access_token_fetcher.h"
 #include "ash/components/account_manager/account_manager.h"
 #include "ash/components/account_manager/account_manager_ui.h"
+#include "base/bind.h"
 #include "base/callback.h"
-#include "base/notreached.h"
+#include "base/callback_forward.h"
+#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "chromeos/crosapi/mojom/account_manager.mojom.h"
+#include "components/account_manager_core/account.h"
 #include "components/account_manager_core/account_addition_result.h"
 #include "components/account_manager_core/account_manager_util.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
+namespace crosapi {
+
 namespace {
 
 void MarshalAccounts(
-    crosapi::mojom::AccountManager::GetAccountsCallback callback,
+    mojom::AccountManager::GetAccountsCallback callback,
     const std::vector<account_manager::Account>& accounts_to_marshal) {
-  std::vector<crosapi::mojom::AccountPtr> mojo_accounts;
+  std::vector<mojom::AccountPtr> mojo_accounts;
   for (const account_manager::Account& account : accounts_to_marshal) {
     mojo_accounts.emplace_back(account_manager::ToMojoAccount(account));
   }
@@ -27,8 +39,7 @@
 }
 
 void ReportErrorStatusFromHasDummyGaiaToken(
-    base::OnceCallback<void(crosapi::mojom::GoogleServiceAuthErrorPtr)>
-        callback,
+    base::OnceCallback<void(mojom::GoogleServiceAuthErrorPtr)> callback,
     bool has_dummy_token) {
   GoogleServiceAuthError error(GoogleServiceAuthError::AuthErrorNone());
   if (has_dummy_token) {
@@ -41,8 +52,6 @@
 
 }  // namespace
 
-namespace crosapi {
-
 AccountManagerAsh::AccountManagerAsh(ash::AccountManager* account_manager)
     : account_manager_(account_manager) {
   DCHECK(account_manager_);
@@ -125,6 +134,24 @@
   account_manager_ui_->ShowManageAccountsSettings();
 }
 
+void AccountManagerAsh::CreateAccessTokenFetcher(
+    mojom::AccountKeyPtr mojo_account_key,
+    const std::string& oauth_consumer_name,
+    CreateAccessTokenFetcherCallback callback) {
+  // TODO(https://crbug.com/1175741): Add metrics.
+  VLOG(1) << "Received a request for access token from: "
+          << oauth_consumer_name;
+
+  mojo::PendingRemote<mojom::AccessTokenFetcher> pending_remote;
+  auto access_token_fetcher = std::make_unique<AccessTokenFetcher>(
+      account_manager_, std::move(mojo_account_key), /*done_closure=*/
+      base::BindOnce(&AccountManagerAsh::DeletePendingAccessTokenFetchRequest,
+                     weak_ptr_factory_.GetWeakPtr()),
+      /*receiver=*/pending_remote.InitWithNewPipeAndPassReceiver());
+  pending_access_token_requests_.emplace_back(std::move(access_token_fetcher));
+  std::move(callback).Run(std::move(pending_remote));
+}
+
 void AccountManagerAsh::OnTokenUpserted(
     const account_manager::Account& account) {
   for (auto& observer : observers_)
@@ -164,8 +191,23 @@
       .Run(ToMojoAccountAdditionResult(result));
 }
 
+void AccountManagerAsh::DeletePendingAccessTokenFetchRequest(
+    AccessTokenFetcher* request) {
+  pending_access_token_requests_.erase(
+      std::remove_if(
+          pending_access_token_requests_.begin(),
+          pending_access_token_requests_.end(),
+          [&request](const std::unique_ptr<AccessTokenFetcher>& pending_request)
+              -> bool { return pending_request.get() == request; }),
+      pending_access_token_requests_.end());
+}
+
 void AccountManagerAsh::FlushMojoForTesting() {
   observers_.FlushForTesting();
 }
 
+int AccountManagerAsh::GetNumPendingAccessTokenRequests() const {
+  return pending_access_token_requests_.size();
+}
+
 }  // namespace crosapi
diff --git a/ash/components/account_manager/account_manager_ash.h b/ash/components/account_manager/account_manager_ash.h
index 9bd97dfc..ff6c9bf 100644
--- a/ash/components/account_manager/account_manager_ash.h
+++ b/ash/components/account_manager/account_manager_ash.h
@@ -5,6 +5,10 @@
 #ifndef ASH_COMPONENTS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_ASH_H_
 #define ASH_COMPONENTS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_ASH_H_
 
+#include <memory>
+#include <vector>
+
+#include "ash/components/account_manager/access_token_fetcher.h"
 #include "ash/components/account_manager/account_manager.h"
 #include "ash/components/account_manager/account_manager_ui.h"
 #include "base/callback_forward.h"
@@ -50,6 +54,10 @@
   void ShowReauthAccountDialog(const std::string& email,
                                base::OnceClosure closure) override;
   void ShowManageAccountsSettings() override;
+  void CreateAccessTokenFetcher(
+      mojom::AccountKeyPtr mojo_account_key,
+      const std::string& oauth_consumer_name,
+      CreateAccessTokenFetcherCallback callback) override;
 
   // ash::AccountManager::Observer:
   void OnTokenUpserted(const account_manager::Account& account) override;
@@ -68,14 +76,24 @@
   // A callback for `AccountManagerUI::ShowAccountAdditionDialog`.
   void OnAddAccountDialogClosed();
   void FinishAddAccount(const account_manager::AccountAdditionResult& result);
+  // Deletes `request` from `pending_access_token_requests_`, if present.
+  void DeletePendingAccessTokenFetchRequest(AccessTokenFetcher* request);
+
   void FlushMojoForTesting();
+  int GetNumPendingAccessTokenRequests() const;
 
   ShowAddAccountDialogCallback account_addition_callback_;
   bool account_addition_in_progress_ = false;
   ash::AccountManager* const account_manager_;
+  std::unique_ptr<ash::AccountManagerUI> account_manager_ui_;
+  std::vector<std::unique_ptr<AccessTokenFetcher>>
+      pending_access_token_requests_;
+
+  // Don't add new members below this. `receivers_` and `observers_` should be
+  // destroyed as soon as `this` is getting destroyed so that we don't deal
+  // with message handling on a partially destroyed object.
   mojo::ReceiverSet<mojom::AccountManager> receivers_;
   mojo::RemoteSet<mojom::AccountManagerObserver> observers_;
-  std::unique_ptr<ash::AccountManagerUI> account_manager_ui_;
 
   base::WeakPtrFactory<AccountManagerAsh> weak_ptr_factory_{this};
 };
diff --git a/ash/components/account_manager/account_manager_ash_unittest.cc b/ash/components/account_manager/account_manager_ash_unittest.cc
index b8e3cba..09bc5f8 100644
--- a/ash/components/account_manager/account_manager_ash_unittest.cc
+++ b/ash/components/account_manager/account_manager_ash_unittest.cc
@@ -6,11 +6,14 @@
 
 #include <cstddef>
 #include <memory>
+#include <string>
 #include <utility>
 #include <vector>
 
+#include "ash/components/account_manager/access_token_fetcher.h"
 #include "ash/components/account_manager/account_manager.h"
 #include "ash/components/account_manager/account_manager_ui.h"
+#include "base/bind.h"
 #include "base/callback_forward.h"
 #include "base/callback_helpers.h"
 #include "base/optional.h"
@@ -19,9 +22,14 @@
 #include "base/test/task_environment.h"
 #include "chromeos/crosapi/mojom/account_manager.mojom-test-utils.h"
 #include "chromeos/crosapi/mojom/account_manager.mojom.h"
+#include "components/account_manager_core/account.h"
 #include "components/account_manager_core/account_manager_util.h"
 #include "components/prefs/testing_pref_service.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/test/test_url_loader_factory.h"
@@ -34,6 +42,16 @@
 const char kFakeGaiaId[] = "fake-gaia-id";
 const char kFakeEmail[] = "fake_email@example.com";
 const char kFakeToken[] = "fake-token";
+const char kFakeOAuthConsumerName[] = "fake-oauth-consumer-name";
+constexpr char kFakeAccessToken[] = "fake-access-token";
+// Same access token value as above in `kFakeAccessToken`.
+constexpr char kAccessTokenResponse[] = R"(
+    {
+      "access_token": "fake-access-token",
+      "expires_in": 3600,
+      "token_type": "Bearer",
+      "id_token": "id_token"
+    })";
 const account_manager::Account kFakeAccount = account_manager::Account{
     account_manager::AccountKey{kFakeGaiaId,
                                 account_manager::AccountType::kGaia},
@@ -58,15 +76,15 @@
     receiver_.Bind(std::move(receiver));
   }
 
-  int GetNumOnTokenUpsertedCalls() { return num_token_upserted_calls_; }
+  int GetNumOnTokenUpsertedCalls() const { return num_token_upserted_calls_; }
 
-  account_manager::Account GetLastUpsertedAccount() {
+  account_manager::Account GetLastUpsertedAccount() const {
     return last_upserted_account_;
   }
 
-  int GetNumOnAccountRemovedCalls() { return num_account_removed_calls_; }
+  int GetNumOnAccountRemovedCalls() const { return num_account_removed_calls_; }
 
-  account_manager::Account GetLastRemovedAccount() {
+  account_manager::Account GetLastRemovedAccount() const {
     return last_removed_account_;
   }
 
@@ -95,10 +113,10 @@
 
 class FakeAccountManagerUI : public ash::AccountManagerUI {
  public:
-  FakeAccountManagerUI() {}
+  FakeAccountManagerUI() = default;
   FakeAccountManagerUI(const FakeAccountManagerUI&) = delete;
   FakeAccountManagerUI& operator=(const FakeAccountManagerUI&) = delete;
-  ~FakeAccountManagerUI() override {}
+  ~FakeAccountManagerUI() override = default;
 
   void SetIsDialogShown(bool is_dialog_shown) {
     is_dialog_shown_ = is_dialog_shown;
@@ -130,6 +148,7 @@
     show_account_addition_dialog_calls_++;
     is_dialog_shown_ = true;
   }
+
   void ShowReauthAccountDialog(
       const std::string& email,
       base::OnceClosure close_dialog_closure) override {
@@ -137,7 +156,9 @@
     show_account_reauthentication_dialog_calls_++;
     is_dialog_shown_ = true;
   }
+
   bool IsDialogShown() override { return is_dialog_shown_; }
+
   void ShowManageAccountsSettings() override {
     show_manage_accounts_settings_calls_++;
   }
@@ -149,6 +170,36 @@
   int show_manage_accounts_settings_calls_ = 0;
 };
 
+// A test spy for intercepting AccountManager calls.
+class AccountManagerSpy : public ash::AccountManager {
+ public:
+  AccountManagerSpy() = default;
+  AccountManagerSpy(const AccountManagerSpy&) = delete;
+  AccountManagerSpy& operator=(const AccountManagerSpy&) = delete;
+  ~AccountManagerSpy() override = default;
+
+  // ash::AccountManager override:
+  std::unique_ptr<OAuth2AccessTokenFetcher> CreateAccessTokenFetcher(
+      const ::account_manager::AccountKey& account_key,
+      OAuth2AccessTokenConsumer* consumer) const override {
+    num_access_token_fetches_++;
+    last_access_token_account_key_ = account_key;
+
+    return ash::AccountManager::CreateAccessTokenFetcher(account_key, consumer);
+  }
+
+  int num_access_token_fetches() const { return num_access_token_fetches_; }
+
+  account_manager::AccountKey last_access_token_account_key() const {
+    return last_access_token_account_key_;
+  }
+
+ private:
+  // Mutated by const CreateAccessTokenFetcher.
+  mutable int num_access_token_fetches_ = 0;
+  mutable account_manager::AccountKey last_access_token_account_key_;
+};
+
 class AccountManagerAshTest : public ::testing::Test {
  public:
   AccountManagerAshTest() = default;
@@ -219,20 +270,60 @@
     account_manager_ash_->ShowManageAccountsSettings();
   }
 
-  int GetNumObservers() { return account_manager_ash_->observers_.size(); }
+  mojom::AccessTokenResultPtr FetchAccessToken(
+      const account_manager::AccountKey& account_key) {
+    return FetchAccessToken(account_key, /*scopes=*/{});
+  }
+
+  mojom::AccessTokenResultPtr FetchAccessToken(
+      const account_manager::AccountKey& account_key,
+      const std::vector<std::string>& scopes) {
+    mojo::PendingRemote<mojom::AccessTokenFetcher> pending_remote;
+    account_manager_async_waiter()->CreateAccessTokenFetcher(
+        account_manager::ToMojoAccountKey(account_key), kFakeOAuthConsumerName,
+        &pending_remote);
+    mojo::Remote<mojom::AccessTokenFetcher> remote(std::move(pending_remote));
+
+    base::RunLoop run_loop;
+    mojom::AccessTokenResultPtr result;
+    remote->Start(
+        scopes,
+        base::BindLambdaForTesting(
+            [&run_loop, &result](mojom::AccessTokenResultPtr returned_result) {
+              result = std::move(returned_result);
+              run_loop.Quit();
+            }));
+    run_loop.Run();
+
+    return result;
+  }
+
+  void AddFakeAccessTokenResponse() {
+    GURL url(GaiaUrls::GetInstance()->oauth2_token_url());
+    test_url_loader_factory_.AddResponse(url.spec(), kAccessTokenResponse,
+                                         net::HTTP_OK);
+  }
+
+  int GetNumObservers() const {
+    return account_manager_ash_->observers_.size();
+  }
+
+  int GetNumPendingAccessTokenRequests() const {
+    return account_manager_ash_->GetNumPendingAccessTokenRequests();
+  }
 
   mojom::AccountManagerAsyncWaiter* account_manager_async_waiter() {
     return account_manager_async_waiter_.get();
   }
 
-  ash::AccountManager* account_manager() { return &account_manager_; }
+  AccountManagerSpy* account_manager() { return &account_manager_; }
 
  private:
   base::test::SingleThreadTaskEnvironment task_environment_;
 
   network::TestURLLoaderFactory test_url_loader_factory_;
   TestingPrefServiceSimple pref_service_;
-  ash::AccountManager account_manager_;
+  AccountManagerSpy account_manager_;
   mojo::Remote<mojom::AccountManager> remote_;
   std::unique_ptr<AccountManagerAsh> account_manager_ash_;
   std::unique_ptr<mojom::AccountManagerAsyncWaiter>
@@ -508,4 +599,110 @@
             GetFakeAccountManagerUI()->show_manage_accounts_settings_calls());
 }
 
+TEST_F(AccountManagerAshTest,
+       FetchingAccessTokenResultsInErrorForInvalidAccountKey) {
+  ASSERT_TRUE(InitializeAccountManager());
+  EXPECT_EQ(0, GetNumPendingAccessTokenRequests());
+  account_manager::AccountKey account_key{std::string(),
+                                          account_manager::AccountType::kGaia};
+  mojom::AccessTokenResultPtr result = FetchAccessToken(account_key);
+
+  ASSERT_TRUE(result->is_error());
+  EXPECT_EQ(mojom::GoogleServiceAuthError::State::kUserNotSignedUp,
+            result->get_error()->state);
+
+  // Check that requests are not leaking.
+  RunAllPendingTasks();
+  EXPECT_EQ(0, GetNumPendingAccessTokenRequests());
+}
+
+TEST_F(AccountManagerAshTest,
+       FetchingAccessTokenResultsInErrorForActiveDirectoryAccounts) {
+  ASSERT_TRUE(InitializeAccountManager());
+  EXPECT_EQ(0, GetNumPendingAccessTokenRequests());
+  account_manager::AccountKey account_key{
+      kFakeGaiaId, account_manager::AccountType::kActiveDirectory};
+  mojom::AccessTokenResultPtr result = FetchAccessToken(account_key);
+
+  ASSERT_TRUE(result->is_error());
+  EXPECT_EQ(mojom::GoogleServiceAuthError::State::kUserNotSignedUp,
+            result->get_error()->state);
+
+  // Check that requests are not leaking.
+  RunAllPendingTasks();
+  EXPECT_EQ(0, GetNumPendingAccessTokenRequests());
+}
+
+TEST_F(AccountManagerAshTest,
+       FetchingAccessTokenResultsInErrorForUnknownAccountKey) {
+  ASSERT_TRUE(InitializeAccountManager());
+  EXPECT_EQ(0, GetNumPendingAccessTokenRequests());
+  account_manager::AccountKey account_key{kFakeGaiaId,
+                                          account_manager::AccountType::kGaia};
+  mojom::AccessTokenResultPtr result = FetchAccessToken(account_key);
+
+  ASSERT_TRUE(result->is_error());
+  EXPECT_EQ(mojom::GoogleServiceAuthError::State::kUserNotSignedUp,
+            result->get_error()->state);
+
+  // Check that requests are not leaking.
+  RunAllPendingTasks();
+  EXPECT_EQ(0, GetNumPendingAccessTokenRequests());
+}
+
+TEST_F(AccountManagerAshTest, FetchAccessTokenRequestsCanBeCancelled) {
+  // Setup.
+  ASSERT_TRUE(InitializeAccountManager());
+  account_manager::AccountKey account_key{kFakeGaiaId,
+                                          account_manager::AccountType::kGaia};
+  account_manager()->UpsertAccount(account_key, kFakeEmail, kFakeToken);
+  mojo::PendingRemote<mojom::AccessTokenFetcher> pending_remote;
+  EXPECT_EQ(0, GetNumPendingAccessTokenRequests());
+  account_manager_async_waiter()->CreateAccessTokenFetcher(
+      account_manager::ToMojoAccountKey(account_key), kFakeOAuthConsumerName,
+      &pending_remote);
+  mojo::Remote<mojom::AccessTokenFetcher> remote(std::move(pending_remote));
+  mojom::AccessTokenResultPtr result;
+  EXPECT_TRUE(result.is_null());
+  // Make a request to fetch access token. Since we haven't setup our test URL
+  // loader factory via `AddFakeAccessTokenResponse`, this request will never be
+  // completed.
+  remote->Start(/*scopes=*/{},
+                base::BindLambdaForTesting(
+                    [&result](mojom::AccessTokenResultPtr returned_result) {
+                      result = std::move(returned_result);
+                    }));
+  EXPECT_EQ(1, GetNumPendingAccessTokenRequests());
+
+  // Test.
+  // This should cancel the pending request.
+  remote.reset();
+  RunAllPendingTasks();
+  EXPECT_EQ(0, GetNumPendingAccessTokenRequests());
+  // Verify that result is still unset - i.e. the pending request was cancelled,
+  // and didn't complete normally.
+  EXPECT_TRUE(result.is_null());
+}
+
+TEST_F(AccountManagerAshTest, FetchAccessToken) {
+  constexpr char kFakeScope[] = "fake-scope";
+  ASSERT_TRUE(InitializeAccountManager());
+  account_manager::AccountKey account_key{kFakeGaiaId,
+                                          account_manager::AccountType::kGaia};
+  account_manager()->UpsertAccount(account_key, kFakeEmail, kFakeToken);
+  AddFakeAccessTokenResponse();
+  EXPECT_EQ(0, GetNumPendingAccessTokenRequests());
+  mojom::AccessTokenResultPtr result =
+      FetchAccessToken(account_key, {kFakeScope});
+
+  ASSERT_TRUE(result->is_access_token_info());
+  EXPECT_EQ(kFakeAccessToken, result->get_access_token_info()->access_token);
+  EXPECT_EQ(1, account_manager()->num_access_token_fetches());
+  EXPECT_EQ(account_key, account_manager()->last_access_token_account_key());
+
+  // Check that requests are not leaking.
+  RunAllPendingTasks();
+  EXPECT_EQ(0, GetNumPendingAccessTokenRequests());
+}
+
 }  // namespace crosapi
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 37ec611..b130a0c0 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -409,6 +409,10 @@
 const base::Feature kHandwritingGestureEditing{
     "HandwritingGestureEditing", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enable showing search results from the help app in the launcher.
+const base::Feature kHelpAppLauncherSearch{"HelpAppLauncherSearch",
+                                           base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enable the search service integration in the Help app.
 const base::Feature kHelpAppSearchServiceIntegration{
     "HelpAppSearchServiceIntegration", base::FEATURE_ENABLED_BY_DEFAULT};
@@ -466,7 +470,7 @@
 
 // Controls whether new OOBE layout is shown or not.
 const base::Feature kNewOobeLayout{"NewOobeLayout",
-                                   base::FEATURE_DISABLED_BY_DEFAULT};
+                                   base::FEATURE_ENABLED_BY_DEFAULT};
 
 // ChromeOS Media App. https://crbug.com/996088.
 const base::Feature kMediaApp{"MediaApp", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index dbebc80..114e0c4f 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -186,6 +186,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kGesturePropertiesDBusService;
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kHelpAppLauncherSearch;
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kHelpAppSearchServiceIntegration;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kImeMojoDecoder;
diff --git a/ash/constants/ash_switches.cc b/ash/constants/ash_switches.cc
index 391b1628..f43ee61 100644
--- a/ash/constants/ash_switches.cc
+++ b/ash/constants/ash_switches.cc
@@ -242,6 +242,14 @@
 const char kDisableOOBEChromeVoxHintTimerForTesting[] =
     "disable-oobe-chromevox-hint-timer-for-testing";
 
+// Enables the ChromeVox hint in OOBE for dev mode. This flag is used
+// to override the default dev mode behavior of disabling the feature.
+// If both kEnableOOBEChromeVoxHintForDevMode and
+// kDisableOOBEChromeVoxHintTimerForTesting are present, the ChromeVox hint
+// will be disabled, since the latter flag takes precedence over the former.
+const char kEnableOOBEChromeVoxHintForDevMode[] =
+    "enable-oobe-chromevox-hint-timer-for-dev-mode";
+
 // Disables per-user timezone.
 const char kDisablePerUserTimezone[] = "disable-per-user-timezone";
 
@@ -620,6 +628,9 @@
 // only be used for lacros_chrome_browsertests that requires ml service.
 const char kUseFakeMLServiceForTest[] = "use-fake-ml-service-for-test";
 
+// Enables configuring the OEM Device Requsition in the OOBE.
+const char kEnableRequisitionEdits[] = "enable-requisition-edits";
+
 bool MemoryPressureHandlingEnabled() {
   if (base::FieldTrialList::FindFullName(kMemoryPressureExperimentName) ==
       kMemoryPressureHandlingOff) {
@@ -699,5 +710,15 @@
       kDisableOOBEChromeVoxHintTimerForTesting);
 }
 
+bool IsOOBEChromeVoxHintEnabledForDevMode() {
+  return base::CommandLine::ForCurrentProcess()->HasSwitch(
+      kEnableOOBEChromeVoxHintForDevMode);
+}
+
+bool IsDeviceRequisitionConfigurable() {
+  return base::CommandLine::ForCurrentProcess()->HasSwitch(
+      kEnableRequisitionEdits);
+}
+
 }  // namespace switches
 }  // namespace chromeos
diff --git a/ash/constants/ash_switches.h b/ash/constants/ash_switches.h
index 613c413e..b75af1c 100644
--- a/ash/constants/ash_switches.h
+++ b/ash/constants/ash_switches.h
@@ -87,6 +87,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kDisableOOBEChromeVoxHintTimerForTesting[];
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kEnableOOBEChromeVoxHintForDevMode[];
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kDisablePerUserTimezone[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kDisableRollbackOption[];
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -111,6 +113,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kEnableNdkTranslation64[];
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kEnableRequisitionEdits[];
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kEnableRequestTabletSite[];
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kEnableTouchCalibrationSetting[];
@@ -284,6 +288,14 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsOOBEChromeVoxHintTimerDisabledForTesting();
 
+// Returns true if the OOBE ChromeVox hint is enabled for dev mode.
+COMPONENT_EXPORT(ASH_CONSTANTS)
+bool IsOOBEChromeVoxHintEnabledForDevMode();
+
+// Returns true if the OEM Device Requisition can be configured.
+COMPONENT_EXPORT(ASH_CONSTANTS)
+bool IsDeviceRequisitionConfigurable();
+
 }  // namespace switches
 }  // namespace chromeos
 
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 8604b7b3..58b06b22 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -1383,8 +1383,8 @@
 
   UpdateBottomStatusIndicatorVisibility();
 
-  if (!oobe_dialog_visible_ && primary_big_view_)
-    primary_big_view_->RequestFocus();
+  if (!oobe_dialog_visible_ && CurrentBigUserView())
+    CurrentBigUserView()->RequestFocus();
 }
 
 void LockContentsView::MaybeUpdateExpandedView(const AccountId& account_id,
diff --git a/ash/public/cpp/holding_space/holding_space_metrics.cc b/ash/public/cpp/holding_space/holding_space_metrics.cc
index 36db671c..a29a962 100644
--- a/ash/public/cpp/holding_space/holding_space_metrics.cc
+++ b/ash/public/cpp/holding_space/holding_space_metrics.cc
@@ -7,7 +7,6 @@
 #include <map>
 #include <string>
 
-#include "ash/public/cpp/holding_space/holding_space_item.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
 #include "base/time/time.h"
@@ -102,6 +101,10 @@
   }
 }
 
+void RecordItemFailureToLaunch(HoldingSpaceItem::Type type) {
+  base::UmaHistogramEnumeration("HoldingSpace.Item.FailureToLaunch", type);
+}
+
 void RecordTimeFromFirstAvailabilityToFirstAdd(base::TimeDelta time_delta) {
   // NOTE: 24 days appears to be the max supported number of days.
   base::UmaHistogramCustomTimes(
diff --git a/ash/public/cpp/holding_space/holding_space_metrics.h b/ash/public/cpp/holding_space/holding_space_metrics.h
index ae46aac..512c514 100644
--- a/ash/public/cpp/holding_space/holding_space_metrics.h
+++ b/ash/public/cpp/holding_space/holding_space_metrics.h
@@ -8,15 +8,13 @@
 #include <vector>
 
 #include "ash/public/cpp/ash_public_export.h"
+#include "ash/public/cpp/holding_space/holding_space_item.h"
 
 namespace base {
 class TimeDelta;
 }  // namespace base
 
 namespace ash {
-
-class HoldingSpaceItem;
-
 namespace holding_space_metrics {
 
 // Enumeration of actions that can be taken on the holding space pod in the
@@ -83,6 +81,9 @@
 ASH_PUBLIC_EXPORT void RecordItemCounts(
     const std::vector<const HoldingSpaceItem*>& items);
 
+// Records a failure to launch a holding space item of the specified `type`.
+ASH_PUBLIC_EXPORT void RecordItemFailureToLaunch(HoldingSpaceItem::Type type);
+
 // Records time from the first availability of the holding space feature to the
 // first item being added to holding space.
 ASH_PUBLIC_EXPORT void RecordTimeFromFirstAvailabilityToFirstAdd(
diff --git a/ash/shelf/hotseat_widget_unittest.cc b/ash/shelf/hotseat_widget_unittest.cc
index 240b699..deb0f4d 100644
--- a/ash/shelf/hotseat_widget_unittest.cc
+++ b/ash/shelf/hotseat_widget_unittest.cc
@@ -34,6 +34,7 @@
 #include "ash/shelf/test/widget_animation_smoothness_inspector.h"
 #include "ash/shelf/test/widget_animation_waiter.h"
 #include "ash/shell.h"
+#include "ash/system/ime_menu/ime_menu_tray.h"
 #include "ash/system/overview/overview_button_tray.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/unified/unified_system_tray.h"
@@ -1499,6 +1500,36 @@
             GetShelfLayoutManager()->auto_hide_state());
 }
 
+// Tests that the hotseat hides when it is in kExtendedMode and a status area
+// tray bubble is shown.
+TEST_P(HotseatWidgetTest, DismissHotseatWhenStatusAreaTrayShows) {
+  TabletModeControllerTestApi().EnterTabletMode();
+  std::unique_ptr<aura::Window> window =
+      AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
+  wm::ActivateWindow(window.get());
+  StatusAreaWidget* status_area_widget = GetShelfWidget()->status_area_widget();
+  status_area_widget->ime_menu_tray()->SetVisiblePreferred(true);
+
+  // Show the hotseat.
+  SwipeUpOnShelf();
+  ASSERT_EQ(HotseatState::kExtended, GetShelfLayoutManager()->hotseat_state());
+  EXPECT_FALSE(status_area_widget->ime_menu_tray()->GetBubbleView());
+
+  // Show the ime menu tray bubble, and wait for the hotseat to be hidden.
+  GetEventGenerator()->GestureTapAt(
+      status_area_widget->ime_menu_tray()->GetBoundsInScreen().CenterPoint());
+  base::RunLoop().RunUntilIdle();
+
+  // The hotseat should be hidden and the tray bubble should be shown.
+  EXPECT_EQ(HotseatState::kHidden, GetShelfLayoutManager()->hotseat_state());
+  EXPECT_TRUE(status_area_widget->ime_menu_tray()->GetBubbleView());
+
+  // Swiping up on the shelf should hide the tray bubble and extend the hotseat.
+  SwipeUpOnShelf();
+  EXPECT_FALSE(status_area_widget->ime_menu_tray()->GetBubbleView());
+  EXPECT_EQ(HotseatState::kExtended, GetShelfLayoutManager()->hotseat_state());
+}
+
 // Tests that the work area updates once each when going to/from tablet mode
 // with no windows open.
 TEST_P(HotseatWidgetTest, WorkAreaUpdatesClamshellToFromHomeLauncherNoWindows) {
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index 79afaa56..f3eb5b5 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -73,7 +73,6 @@
 #include "ui/events/event_handler.h"
 #include "ui/events/gesture_event_details.h"
 #include "ui/events/types/event_type.h"
-#include "ui/message_center/message_center.h"
 #include "ui/views/border.h"
 #include "ui/views/widget/widget.h"
 #include "ui/wm/core/coordinate_conversion.h"
@@ -349,6 +348,23 @@
   manager_->ResumeWorkAreaUpdate();
 }
 
+// ShelfLayoutManager::ScopedVisibilityLock ------------------------------------
+
+ShelfLayoutManager::ScopedVisibilityLock::ScopedVisibilityLock(
+    ShelfLayoutManager* shelf)
+    : shelf_(shelf->weak_factory_.GetWeakPtr()) {
+  ++shelf_->suspend_visibility_update_;
+}
+
+ShelfLayoutManager::ScopedVisibilityLock::~ScopedVisibilityLock() {
+  if (!shelf_)
+    return;
+  --shelf_->suspend_visibility_update_;
+  DCHECK_GE(shelf_->suspend_visibility_update_, 0);
+  if (shelf_->suspend_visibility_update_ == 0)
+    shelf_->UpdateVisibilityState();
+}
+
 // ShelfLayoutManager ----------------------------------------------------------
 
 ShelfLayoutManager::ShelfLayoutManager(ShelfWidget* shelf_widget, Shelf* shelf)
@@ -394,7 +410,6 @@
   shelf_background_type_ = GetShelfBackgroundType();
   wallpaper_controller_observation_.Observe(shell->wallpaper_controller());
   display::Screen::GetScreen()->AddObserver(this);
-  message_center::MessageCenter::Get()->AddObserver(this);
 
   // DesksController could be null when virtual desks feature is not enabled.
   if (DesksController::Get())
@@ -419,8 +434,6 @@
 
   SplitViewController::Get(shelf_widget_->GetNativeWindow())
       ->RemoveObserver(this);
-
-  message_center::MessageCenter::Get()->RemoveObserver(this);
 }
 
 bool ShelfLayoutManager::IsVisible() const {
@@ -1149,23 +1162,6 @@
 ////////////////////////////////////////////////////////////////////////////////
 // ShelfLayoutManager, private:
 
-void ShelfLayoutManager::OnCenterVisibilityChanged(
-    message_center::Visibility visibility) {
-  // Uses base::CancelableOnceClosure to handle two edge cases: (1)
-  // ShelfLayoutManager is destructed before the callback runs. (2) The previous
-  // callback is still pending.
-  visibility_update_for_tray_callback_.Reset(base::BindOnce(
-      &ShelfLayoutManager::UpdateVisibilityStateForSystemTrayChange,
-      base::Unretained(this), visibility));
-
-  // OnCenterVisibilityChanged is called when the visibility of system tray
-  // is set, which is before the tray bubble is created/destructed. Meanwhile,
-  // we rely on the state of tray bubble to calculate the auto-hide state.
-  // Use ThreadTaskRunnerHandle to specify that the task runs on the UI thread.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, visibility_update_for_tray_callback_.callback());
-}
-
 void ShelfLayoutManager::SuspendWorkAreaUpdate() {
   ++suspend_work_area_update_;
 }
@@ -1988,6 +1984,12 @@
     return false;
   }
 
+  // The shelf should be shown when any bubble in the status area is shown.
+  if (shelf_widget_->status_area_widget() &&
+      shelf_widget_->status_area_widget()->ShouldShowShelf()) {
+    return false;
+  }
+
   const aura::Window* fullscreen_window = GetWindowForFullscreenModeInRoot(
       shelf_widget_->GetNativeWindow()->GetRootWindow());
   return fullscreen_window &&
@@ -2679,18 +2681,34 @@
   return window_drag_controller_ && window_drag_controller_->drag_started();
 }
 
-void ShelfLayoutManager::UpdateVisibilityStateForSystemTrayChange(
-    message_center::Visibility visibility) {
+void ShelfLayoutManager::UpdateVisibilityStateForTrayBubbleChange(
+    bool bubble_shown) {
   base::Optional<base::AutoReset<bool>> reset;
 
   // Hides the hotseat when the hotseat is in kExtended mode and the system tray
   // shows.
-  if (visibility == message_center::Visibility::VISIBILITY_MESSAGE_CENTER &&
-      hotseat_state() == HotseatState::kExtended) {
+  if (bubble_shown && hotseat_state() == HotseatState::kExtended) {
     reset.emplace(&should_hide_hotseat_, true);
   }
 
   UpdateVisibilityState();
 }
 
+void ShelfLayoutManager::OnShelfTrayBubbleVisibilityChanged(bool bubble_shown) {
+  // Uses base::CancelableOnceClosure to handle two edge cases: (1)
+  // ShelfLayoutManager is destructed before the callback runs. (2) The previous
+  // callback is still pending.
+  visibility_update_for_tray_callback_.Reset(base::BindOnce(
+      &ShelfLayoutManager::UpdateVisibilityStateForTrayBubbleChange,
+      base::Unretained(this), /*bubble_shown=*/bubble_shown));
+
+  // OnShelfTrayBubbleVisibilityChanged is called when the visibility of a
+  // status area tray bubble is set, which is before the tray bubble is
+  // created/destructed. Meanwhile, we rely on the state of tray bubble to
+  // calculate the auto-hide state.
+  // Use ThreadTaskRunnerHandle to specify that the task runs on the UI thread.
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, visibility_update_for_tray_callback_.callback());
+}
+
 }  // namespace ash
diff --git a/ash/shelf/shelf_layout_manager.h b/ash/shelf/shelf_layout_manager.h
index e4b62f78..46d8b8f9 100644
--- a/ash/shelf/shelf_layout_manager.h
+++ b/ash/shelf/shelf_layout_manager.h
@@ -28,6 +28,7 @@
 #include "ash/wm/wm_default_layout_manager.h"
 #include "ash/wm/workspace/workspace_types.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/optional.h"
 #include "base/scoped_observation.h"
@@ -38,7 +39,6 @@
 #include "ui/display/display_observer.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect.h"
-#include "ui/message_center/message_center_observer.h"
 #include "ui/wm/public/activation_change_observer.h"
 
 namespace ui {
@@ -82,7 +82,6 @@
       public WallpaperControllerObserver,
       public LocaleChangeObserver,
       public DesksController::Observer,
-      public message_center::MessageCenterObserver,
       public ShelfConfig::Observer {
  public:
   // Suspend work area updates within its scope. Note that relevant
@@ -98,6 +97,17 @@
     DISALLOW_COPY_AND_ASSIGN(ScopedSuspendWorkAreaUpdate);
   };
 
+  // Used to maintain a lock for the shelf visibility state. If locked, then we
+  // should not update the state of the shelf visibility.
+  class ScopedVisibilityLock {
+   public:
+    explicit ScopedVisibilityLock(ShelfLayoutManager* shelf);
+    ~ScopedVisibilityLock();
+
+   private:
+    base::WeakPtr<ShelfLayoutManager> shelf_;
+  };
+
   ShelfLayoutManager(ShelfWidget* shelf_widget, Shelf* shelf);
   ~ShelfLayoutManager() override;
 
@@ -304,6 +314,10 @@
   HotseatState CalculateHotseatState(ShelfVisibilityState visibility_state,
                                      ShelfAutoHideState auto_hide_state) const;
 
+  // Called when the visibility for a tray bubble in the shelf's status area
+  // changes.
+  void OnShelfTrayBubbleVisibilityChanged(bool bubble_shown);
+
  private:
   class UpdateShelfObserver;
   friend class DimShelfLayoutManagerTestBase;
@@ -355,10 +369,6 @@
     kMoving,  // Laying out and animating to target bounds
   };
 
-  // MessageCenterObserver:
-  void OnCenterVisibilityChanged(
-      message_center::Visibility visibility) override;
-
   // Suspends/resumes work area updates.
   void SuspendWorkAreaUpdate();
   void ResumeWorkAreaUpdate();
@@ -505,9 +515,8 @@
   void MaybeCancelWindowDrag();
   bool IsWindowDragInProgress() const;
 
-  // Updates the visibility state because of the change on the system tray.
-  void UpdateVisibilityStateForSystemTrayChange(
-      message_center::Visibility visibility);
+  // Updates the visibility state because of the change on a status area tray.
+  void UpdateVisibilityStateForTrayBubbleChange(bool bubble_shown);
 
   bool in_shutdown_ = false;
 
@@ -675,6 +684,8 @@
   // Records the presentation time for hotseat dragging.
   std::unique_ptr<PresentationTimeRecorder> hotseat_presentation_time_recorder_;
 
+  base::WeakPtrFactory<ShelfLayoutManager> weak_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(ShelfLayoutManager);
 };
 
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc
index c73b25b..2ac3887 100644
--- a/ash/shelf/shelf_layout_manager_unittest.cc
+++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -53,6 +53,7 @@
 #include "ash/shelf/test/shelf_layout_manager_test_base.h"
 #include "ash/shelf/test/widget_animation_waiter.h"
 #include "ash/shell.h"
+#include "ash/system/ime_menu/ime_menu_tray.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/status_area_widget_test_helper.h"
 #include "ash/system/unified/unified_system_tray.h"
@@ -1067,6 +1068,105 @@
   EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
 }
 
+// With a fullscreen window, ensure the hidden shelf is shown temporarily when
+// the app list is shown and when tray bubbles are shown. Ensure that the shelf
+// is hidden again once tray bubbles are closed.
+TEST_F(ShelfLayoutManagerTest, OpenAppListInFullscreenWithShelfHiddenState) {
+  Shelf* shelf = GetPrimaryShelf();
+  StatusAreaWidget* status_area = shelf->status_area_widget();
+  status_area->ime_menu_tray()->SetVisiblePreferred(true);
+
+  // Create a window and make it full screen; the shelf should be hidden.
+  aura::Window* window = CreateTestWindow();
+  window->SetBounds(gfx::Rect(0, 0, 100, 100));
+  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+  window->Show();
+  wm::ActivateWindow(window);
+  GetAppListTestHelper()->CheckVisibility(false);
+  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
+  EXPECT_EQ(WorkspaceWindowState::kFullscreen, GetWorkspaceWindowState());
+  EXPECT_FALSE(GetNonLockScreenContainersContainerLayer()->GetMasksToBounds());
+
+  // Show the app list and the shelf should be temporarily visible.
+  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
+  GetAppListTestHelper()->CheckVisibility(true);
+  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+
+  // Click on the ime menu tray button, to show a tray bubble. The shelf
+  // should still be showing and the app list should hide.
+  ui::test::EventGenerator* generator = GetEventGenerator();
+  generator->MoveMouseTo(
+      status_area->ime_menu_tray()->GetBoundsInScreen().CenterPoint());
+  generator->ClickLeftButton();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+  GetAppListTestHelper()->CheckVisibility(false);
+
+  // Click away from the shelf and tray bubble to hide the shelf.
+  generator->MoveMouseTo(10, 10);
+  generator->ClickLeftButton();
+  EXPECT_TRUE(RunVisibilityUpdateForTrayCallback());
+  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
+
+  // Show the app list and the shelf should be temporarily visible.
+  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
+  GetAppListTestHelper()->CheckVisibility(true);
+  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+
+  // Click on the unified system tray button, opening the tray and hiding the
+  // app list.
+  EXPECT_FALSE(status_area->IsMessageBubbleShown());
+  generator->MoveMouseTo(
+      status_area->unified_system_tray()->GetBoundsInScreen().CenterPoint());
+  generator->ClickLeftButton();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(status_area->IsMessageBubbleShown());
+  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+  GetAppListTestHelper()->CheckVisibility(false);
+
+  // Click away from the shelf and unified system tray to hide the shelf.
+  generator->MoveMouseTo(10, 10);
+  generator->ClickLeftButton();
+  EXPECT_TRUE(RunVisibilityUpdateForTrayCallback());
+  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
+
+  // Show the app list and the shelf should be temporarily visible.
+  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
+  GetAppListTestHelper()->CheckVisibility(true);
+  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+
+  // Click on the unified system tray button, closing the app list.
+  EXPECT_FALSE(status_area->IsMessageBubbleShown());
+  generator->MoveMouseTo(
+      status_area->unified_system_tray()->GetBoundsInScreen().CenterPoint());
+  generator->ClickLeftButton();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(status_area->IsMessageBubbleShown());
+  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+  GetAppListTestHelper()->CheckVisibility(false);
+
+  // Click on the ime menu tray button, to show a tray bubble and close the
+  // unified system tray. The shelf should still be showing.
+  generator->MoveMouseTo(
+      status_area->ime_menu_tray()->GetBoundsInScreen().CenterPoint());
+  generator->ClickLeftButton();
+  EXPECT_FALSE(status_area->IsMessageBubbleShown());
+  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+
+  // Click away from the shelf and tray bubble to hide the shelf.
+  generator->MoveMouseTo(10, 10);
+  generator->ClickLeftButton();
+  EXPECT_TRUE(RunVisibilityUpdateForTrayCallback());
+  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
+}
+
 // Tests the correct behavior of the shelf when there is a system modal window
 // open when we have a single display.
 TEST_F(ShelfLayoutManagerTest, ShelfWithSystemModalWindowSingleDisplay) {
diff --git a/ash/shelf/test/shelf_layout_manager_test_base.cc b/ash/shelf/test/shelf_layout_manager_test_base.cc
index f1964a1..941ed9f7 100644
--- a/ash/shelf/test/shelf_layout_manager_test_base.cc
+++ b/ash/shelf/test/shelf_layout_manager_test_base.cc
@@ -744,4 +744,15 @@
             GetShelfWidget()->GetWindowBoundsInScreen().ToString());
 }
 
+bool ShelfLayoutManagerTestBase::RunVisibilityUpdateForTrayCallback() {
+  if (!GetShelfLayoutManager()
+           ->visibility_update_for_tray_callback_.callback()) {
+    return false;
+  }
+  GetShelfLayoutManager()
+      ->visibility_update_for_tray_callback_.callback()
+      .Run();
+  return true;
+}
+
 }  //  namespace ash
diff --git a/ash/shelf/test/shelf_layout_manager_test_base.h b/ash/shelf/test/shelf_layout_manager_test_base.h
index fb1c3f54..44846773 100644
--- a/ash/shelf/test/shelf_layout_manager_test_base.h
+++ b/ash/shelf/test/shelf_layout_manager_test_base.h
@@ -80,6 +80,10 @@
                                     int delta_y,
                                     bool reverse_scroll);
 
+  // Run the |visibility_update_for_tray_callback_| if set in
+  // ShelfLayoutManager and return true. Otherwise, return false.
+  bool RunVisibilityUpdateForTrayCallback();
+
  private:
   base::TimeTicks timestamp_;
   gfx::Point current_point_;
diff --git a/ash/shortcut_viewer/strings/BUILD.gn b/ash/shortcut_viewer/strings/BUILD.gn
index a2f8a46..37f4642 100644
--- a/ash/shortcut_viewer/strings/BUILD.gn
+++ b/ash/shortcut_viewer/strings/BUILD.gn
@@ -9,6 +9,6 @@
   source = "../shortcut_viewer_strings.grd"
   outputs = [ "grit/shortcut_viewer_strings.h" ] +
             process_file_template(
-                locales_with_fake_bidi,
+                locales_with_pseudolocales,
                 [ "shortcut_viewer_strings_{{source_name_part}}.pak" ])
 }
diff --git a/ash/strings/BUILD.gn b/ash/strings/BUILD.gn
index 48b58a51..12462fc 100644
--- a/ash/strings/BUILD.gn
+++ b/ash/strings/BUILD.gn
@@ -12,7 +12,7 @@
   source = "../ash_strings.grd"
   defines = [ "is_chrome_branded=$is_chrome_branded" ]
   outputs = [ "grit/ash_strings.h" ] +
-            process_file_template(locales_with_fake_bidi,
+            process_file_template(locales_with_pseudolocales,
                                   [ "ash_strings_{{source_name_part}}.pak" ])
 }
 
diff --git a/ash/system/status_area_widget.cc b/ash/system/status_area_widget.cc
index 960b7da..33fd6c1 100644
--- a/ash/system/status_area_widget.cc
+++ b/ash/system/status_area_widget.cc
@@ -48,6 +48,11 @@
 StatusAreaWidget::ScopedTrayBubbleCounter::ScopedTrayBubbleCounter(
     StatusAreaWidget* status_area_widget)
     : status_area_widget_(status_area_widget->weak_ptr_factory_.GetWeakPtr()) {
+  if (status_area_widget_->tray_bubble_count_ == 0) {
+    status_area_widget_->shelf()
+        ->shelf_layout_manager()
+        ->OnShelfTrayBubbleVisibilityChanged(/*bubble_shown=*/true);
+  }
   ++status_area_widget_->tray_bubble_count_;
 }
 
@@ -57,6 +62,12 @@
     return;
 
   --status_area_widget_->tray_bubble_count_;
+  if (status_area_widget_->tray_bubble_count_ == 0) {
+    status_area_widget_->shelf()
+        ->shelf_layout_manager()
+        ->OnShelfTrayBubbleVisibilityChanged(/*bubble_shown=*/false);
+  }
+
   DCHECK_GE(status_area_widget_->tray_bubble_count_, 0);
 }
 
diff --git a/base/allocator/partition_allocator/thread_cache.h b/base/allocator/partition_allocator/thread_cache.h
index 95d85b28..5e0e00e 100644
--- a/base/allocator/partition_allocator/thread_cache.h
+++ b/base/allocator/partition_allocator/thread_cache.h
@@ -228,16 +228,8 @@
   static constexpr float kDefaultMultiplier = 2.;
   static constexpr uint8_t kSmallBucketBaseCount = 64;
 
-  // TODO(crbug.com/1169157): Tune this further.
-  //
-  // From local experiments on Linux with Speedometer 2.0 (see
-  // crbug.com/1169157), renderers make a lot of allocations up to 1024
-  // bytes. The limit was previously set to 512, which is only good to serve
-  // ~81% of allocations in renderers, vs 98% in the browser process. Increasing
-  // this limit to 1024 catches 90% of allocations, that is halves forced misses
-  // vs 512. However, to get to 98%, it is necessary to set the limit at 8192,
-  // which is likely too high across all processes.
-  static constexpr size_t kSizeThreshold = 1024;
+  // TODO(lizeb): Optimize the threshold.
+  static constexpr size_t kSizeThreshold = 512;
 
  private:
   struct Bucket {
diff --git a/base/android/java/src/org/chromium/base/PackageManagerUtils.java b/base/android/java/src/org/chromium/base/PackageManagerUtils.java
index 565b8d1..0d08cc19 100644
--- a/base/android/java/src/org/chromium/base/PackageManagerUtils.java
+++ b/base/android/java/src/org/chromium/base/PackageManagerUtils.java
@@ -49,7 +49,7 @@
      * See {@link PackageManager#queryIntentActivities(Intent, int)}
      */
     public static List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
-        // White-list for Samsung. See http://crbug.com/613977 and https://crbug.com/894160 for more
+        // Allowlist for Samsung. See http://crbug.com/613977 and https://crbug.com/894160 for more
         // context.
         try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
             PackageManager pm = ContextUtils.getApplicationContext().getPackageManager();
diff --git a/base/android/java/src/org/chromium/base/StrictModeContext.java b/base/android/java/src/org/chromium/base/StrictModeContext.java
index 2c9aa80..4d510d8 100644
--- a/base/android/java/src/org/chromium/base/StrictModeContext.java
+++ b/base/android/java/src/org/chromium/base/StrictModeContext.java
@@ -9,7 +9,7 @@
 import java.io.Closeable;
 
 /**
- * Enables try-with-resources compatible StrictMode violation whitelisting.
+ * Enables try-with-resources compatible StrictMode violation allowlisting.
  *
  * Prefer "ignored" as the variable name to appease Android Studio's "Unused symbol" inspection.
  *
diff --git a/base/bind.h b/base/bind.h
index 729f331..29aac56 100644
--- a/base/bind.h
+++ b/base/bind.h
@@ -70,8 +70,8 @@
                      !std::is_const<std::remove_reference_t<Functor>>()),
                 "BindOnce requires non-const rvalue for OnceCallback binding."
                 " I.e.: base::BindOnce(std::move(callback)).");
-#if defined(OS_APPLE) || defined(OS_LINUX) || defined(OS_WIN) || \
-    defined(NCTEST_BIND_ONCE_WITH_PASSED)
+#if defined(OS_APPLE) || defined(OS_CHROMEOS) || defined(OS_LINUX) || \
+    defined(OS_WIN) || defined(NCTEST_BIND_ONCE_WITH_PASSED)
   // TODO(https://crbug.com/1180750): Enable this everywhere.
   static_assert(
       conjunction<
diff --git a/base/bind_internal.h b/base/bind_internal.h
index dace3d0..6da0204 100644
--- a/base/bind_internal.h
+++ b/base/bind_internal.h
@@ -135,7 +135,7 @@
 //
 // Two notes:
 //  1) PassedWrapper supports any type that has a move constructor, however
-//     the type will need to be specifically whitelisted in order for it to be
+//     the type will need to be specifically allowed in order for it to be
 //     bound to a Callback. We guard this explicitly at the call of Passed()
 //     to make for clear errors. Things not given to Passed() will be forwarded
 //     and stored by value which will not work for general move-only types.
diff --git a/base/callback_internal.h b/base/callback_internal.h
index ea0506f2..a4497de4 100644
--- a/base/callback_internal.h
+++ b/base/callback_internal.h
@@ -79,7 +79,7 @@
   friend class CallbackBase;
   friend class CallbackBaseCopyable;
 
-  // Whitelist subclasses that access the destructor of BindStateBase.
+  // Allowlist subclasses that access the destructor of BindStateBase.
   template <typename Functor, typename... BoundArgs>
   friend struct BindState;
   friend struct ::base::FakeBindState;
diff --git a/base/fuchsia/filtered_service_directory.h b/base/fuchsia/filtered_service_directory.h
index da13dcc9..1ead592 100644
--- a/base/fuchsia/filtered_service_directory.h
+++ b/base/fuchsia/filtered_service_directory.h
@@ -27,7 +27,7 @@
   explicit FilteredServiceDirectory(sys::ServiceDirectory* directory);
   ~FilteredServiceDirectory();
 
-  // Adds the specified service to the list of whitelisted services.
+  // Adds the specified service to the list of allowed services.
   void AddService(base::StringPiece service_name);
 
   // Connects a directory client. The directory can be passed to a sandboxed
diff --git a/base/fuchsia/filtered_service_directory_unittest.cc b/base/fuchsia/filtered_service_directory_unittest.cc
index bcbd3ca1..e81879a 100644
--- a/base/fuchsia/filtered_service_directory_unittest.cc
+++ b/base/fuchsia/filtered_service_directory_unittest.cc
@@ -28,7 +28,7 @@
   std::unique_ptr<sys::ServiceDirectory> filtered_client_;
 };
 
-// Verify that we can connect to a whitelisted service.
+// Verify that we can connect to an allowed service.
 TEST_F(FilteredServiceDirectoryTest, Connect) {
   filtered_service_directory_->AddService(testfidl::TestInterface::Name_);
 
@@ -46,7 +46,7 @@
   VerifyTestInterface(&stub2, ZX_OK);
 }
 
-// Verify that non-whitelisted services are blocked.
+// Verify that non-allowed services are blocked.
 TEST_F(FilteredServiceDirectoryTest, ServiceBlocked) {
   auto stub = filtered_client_->Connect<testfidl::TestInterface>();
   VerifyTestInterface(&stub, ZX_ERR_PEER_CLOSED);
diff --git a/base/task/task_traits.h b/base/task/task_traits.h
index 67b8cc2..d4d69e2 100644
--- a/base/task/task_traits.h
+++ b/base/task/task_traits.h
@@ -113,7 +113,10 @@
   // until they're executed. Generally, this should be used only to save
   // critical user data.
   //
-  // Note: Background threads will be promoted to normal threads at shutdown
+  // Note 1: Delayed tasks cannot block shutdown. Delayed tasks posted as part
+  // of a BLOCK_SHUTDOWN sequence will behave like SKIP_ON_SHUTDOWN tasks.
+  //
+  // Note 2: Background threads will be promoted to normal threads at shutdown
   // (i.e. TaskPriority::BEST_EFFORT + TaskShutdownBehavior::BLOCK_SHUTDOWN will
   // resolve without a priority inversion).
   BLOCK_SHUTDOWN,
diff --git a/base/task/thread_pool/task_tracker.cc b/base/task/thread_pool/task_tracker.cc
index a00a2622..e9ee4889 100644
--- a/base/task/thread_pool/task_tracker.cc
+++ b/base/task/thread_pool/task_tracker.cc
@@ -370,8 +370,13 @@
 }
 
 bool TaskTracker::WillPostTaskNow(const Task& task, TaskPriority priority) {
+  // Delayed tasks's TaskShutdownBehavior is implicitly capped at
+  // SKIP_ON_SHUTDOWN. i.e. it cannot BLOCK_SHUTDOWN, TaskTracker will not wait
+  // for a delayed task in a BLOCK_SHUTDOWN TaskSource and will also skip
+  // delayed tasks that happen to become ripe during shutdown.
   if (!task.delayed_run_time.is_null() && state_->HasShutdownStarted())
     return false;
+
   if (has_log_best_effort_tasks_switch_ &&
       priority == TaskPriority::BEST_EFFORT) {
     // A TaskPriority::BEST_EFFORT task is being posted.
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/CloseableOnMainThread.java b/base/test/android/javatests/src/org/chromium/base/test/util/CloseableOnMainThread.java
index ef0094d..2a738d5 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/CloseableOnMainThread.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/CloseableOnMainThread.java
@@ -61,7 +61,7 @@
     }
 
     /**
-     * Enables try-with-resources compatible StrictMode violation whitelisting on android's main
+     * Enables try-with-resources compatible StrictMode violation allowlisting on android's main
      * thread.
      *
      * Prefer "ignored" as the variable name to appease Android Studio's "Unused symbol" inspection.
diff --git a/build/android/apk_operations.pydeps b/build/android/apk_operations.pydeps
index 60b1289..c2c15be 100644
--- a/build/android/apk_operations.pydeps
+++ b/build/android/apk_operations.pydeps
@@ -62,7 +62,6 @@
 ../../third_party/catapult/devil/devil/utils/timeout_retry.py
 ../../third_party/catapult/devil/devil/utils/watchdog_timer.py
 ../../third_party/catapult/devil/devil/utils/zip_utils.py
-../../third_party/catapult/third_party/six/six.py
 ../../third_party/jinja2/__init__.py
 ../../third_party/jinja2/_compat.py
 ../../third_party/jinja2/bccache.py
diff --git a/build/android/devil_chromium.pydeps b/build/android/devil_chromium.pydeps
index 4143805..30f204e6 100644
--- a/build/android/devil_chromium.pydeps
+++ b/build/android/devil_chromium.pydeps
@@ -31,7 +31,6 @@
 ../../third_party/catapult/devil/devil/utils/reraiser_thread.py
 ../../third_party/catapult/devil/devil/utils/timeout_retry.py
 ../../third_party/catapult/devil/devil/utils/watchdog_timer.py
-../../third_party/catapult/third_party/six/six.py
 ../gn_helpers.py
 devil_chromium.py
 pylib/__init__.py
diff --git a/build/android/pylib/local/device/local_device_gtest_run.py b/build/android/pylib/local/device/local_device_gtest_run.py
index c910402..7487701 100644
--- a/build/android/pylib/local/device/local_device_gtest_run.py
+++ b/build/android/pylib/local/device/local_device_gtest_run.py
@@ -580,14 +580,9 @@
     # Delete suspect testcase from tests.
     tests = [test for test in tests if not test in self._crashes]
 
-    batch_size = self._test_instance.test_launcher_batch_limit
+    max_shard_size = self._test_instance.test_launcher_batch_limit
 
-    for i in xrange(0, device_count):
-      unbounded_shard = tests[i::device_count]
-      shards += [
-          unbounded_shard[j:j + batch_size]
-          for j in xrange(0, len(unbounded_shard), batch_size)
-      ]
+    shards.extend(self._PartitionTests(tests, device_count, max_shard_size))
     return shards
 
   #override
diff --git a/build/android/pylib/local/device/local_device_test_run.py b/build/android/pylib/local/device/local_device_test_run.py
index 05128c6..6fa0af7 100644
--- a/build/android/pylib/local/device/local_device_test_run.py
+++ b/build/android/pylib/local/device/local_device_test_run.py
@@ -70,6 +70,10 @@
       SetAppCompatibilityFlagsIfNecessary(self._installed_packages, dev)
       consecutive_device_errors = 0
       for test in tests:
+        if not test:
+          logging.warning('No tests in shared. Continuing.')
+          tests.test_completed()
+          continue
         if exit_now.isSet():
           thread.exit()
 
@@ -247,16 +251,92 @@
       raise InvalidShardingSettings(shard_index, total_shards)
 
     sharded_tests = []
-    for t in self._GroupTests(tests):
-      if (hash(self._GetUniqueTestName(t[0] if isinstance(t, list) else t)) %
-          total_shards == shard_index):
-        if isinstance(t, list):
-          sharded_tests.extend(t)
-        else:
-          sharded_tests.append(t)
 
+    # Group tests by tests that should run in the same test invocation - either
+    # unit tests or batched tests.
+    grouped_tests = self._GroupTests(tests)
+
+    # Partition grouped tests approximately evenly across shards.
+    partitioned_tests = self._PartitionTests(grouped_tests, total_shards,
+                                             float('inf'))
+    if len(partitioned_tests) <= shard_index:
+      return []
+    for t in partitioned_tests[shard_index]:
+      if isinstance(t, list):
+        sharded_tests.extend(t)
+      else:
+        sharded_tests.append(t)
     return sharded_tests
 
+  # Partition tests evenly into |num_desired_partitions| partitions where
+  # possible. However, many constraints make partitioning perfectly impossible.
+  # If the max_partition_size isn't large enough, extra partitions may be
+  # created (infinite max size should always return precisely the desired
+  # number of partitions). Even if the |max_partition_size| is technically large
+  # enough to hold all of the tests in |num_desired_partitions|, we attempt to
+  # keep test order relatively stable to minimize flakes, so when tests are
+  # grouped (eg. batched tests), we cannot perfectly fill all paritions as that
+  # would require breaking up groups.
+  def _PartitionTests(self, tests, num_desired_partitions, max_partition_size):
+    # pylint: disable=no-self-use
+    partitions = []
+
+    # Sort by hash so we don't put all tests in a slow suite in the same
+    # partition.
+    tests = sorted(
+        tests,
+        key=lambda t: hash(
+            self._GetUniqueTestName(t[0] if isinstance(t, list) else t)))
+
+    def CountTestsIndividually(test):
+      if not isinstance(test, list):
+        return False
+      annotations = test[0]['annotations']
+      # UnitTests tests are really fast, so to balance shards better, count
+      # UnitTests Batches as single tests.
+      return ('Batch' not in annotations
+              or annotations['Batch']['value'] != 'UnitTests')
+
+    num_not_yet_allocated = sum(
+        [len(test) - 1 for test in tests if CountTestsIndividually(test)])
+    num_not_yet_allocated += len(tests)
+
+    # Fast linear partition approximation capped by max_partition_size. We
+    # cannot round-robin or otherwise re-order tests dynamically because we want
+    # test order to remain stable.
+    partition_size = min(num_not_yet_allocated // num_desired_partitions,
+                         max_partition_size)
+    partitions.append([])
+    last_partition_size = 0
+    for test in tests:
+      test_count = len(test) if CountTestsIndividually(test) else 1
+      num_not_yet_allocated -= test_count
+      # Make a new shard whenever we would overfill the previous one. However,
+      # if the size of the test group is larger than the max partition size on
+      # its own, just put the group in its own shard instead of splitting up the
+      # group.
+      if (last_partition_size + test_count > partition_size
+          and last_partition_size > 0):
+        num_desired_partitions -= 1
+        partitions.append([])
+        partitions[-1].append(test)
+        last_partition_size = test_count
+        if num_desired_partitions <= 0:
+          # Too many tests for number of partitions, just fill all partitions
+          # beyond num_desired_partitions.
+          partition_size = max_partition_size
+        else:
+          # Re-balance remaining partitions.
+          partition_size = min(num_not_yet_allocated // num_desired_partitions,
+                               max_partition_size)
+      else:
+        partitions[-1].append(test)
+        last_partition_size += test_count
+
+    if not partitions[-1]:
+      partitions.pop()
+    return partitions
+
   def GetTool(self, device):
     if str(device) not in self._tools:
       self._tools[str(device)] = valgrind_tools.CreateTool(
diff --git a/build/android/resource_sizes.pydeps b/build/android/resource_sizes.pydeps
index d956f5b..98d060c 100644
--- a/build/android/resource_sizes.pydeps
+++ b/build/android/resource_sizes.pydeps
@@ -35,7 +35,6 @@
 ../../third_party/catapult/devil/devil/utils/reraiser_thread.py
 ../../third_party/catapult/devil/devil/utils/timeout_retry.py
 ../../third_party/catapult/devil/devil/utils/watchdog_timer.py
-../../third_party/catapult/third_party/six/six.py
 ../../third_party/catapult/third_party/vinn/vinn/__init__.py
 ../../third_party/catapult/third_party/vinn/vinn/_vinn.py
 ../../third_party/catapult/tracing/tracing/__init__.py
diff --git a/build/android/test_runner.pydeps b/build/android/test_runner.pydeps
index 63c30d8..660f8f83 100644
--- a/build/android/test_runner.pydeps
+++ b/build/android/test_runner.pydeps
@@ -95,7 +95,6 @@
 ../../third_party/catapult/devil/devil/utils/timeout_retry.py
 ../../third_party/catapult/devil/devil/utils/watchdog_timer.py
 ../../third_party/catapult/devil/devil/utils/zip_utils.py
-../../third_party/catapult/third_party/six/six.py
 ../../third_party/colorama/src/colorama/__init__.py
 ../../third_party/colorama/src/colorama/ansi.py
 ../../third_party/colorama/src/colorama/ansitowin32.py
diff --git a/build/apple/apple_info_plist.gni b/build/apple/apple_info_plist.gni
deleted file mode 100644
index fe51773d..0000000
--- a/build/apple/apple_info_plist.gni
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/apple/compile_plist.gni")
-
-# The base template used to generate Info.plist files for iOS and Mac apps and
-# frameworks.
-#
-# Arguments
-#
-#     plist_templates:
-#         string array, paths to plist files which will be used for the bundle.
-#
-#     executable_name:
-#         string, name of the generated target used for the product
-#         and executable name as specified in the output Info.plist.
-#
-#     format:
-#         string, the format to `plutil -convert` the plist to when
-#         generating the output.
-#
-#     extra_substitutions:
-#         (optional) string array, 'key=value' pairs for extra fields which are
-#         specified in a source Info.plist template.
-#
-#     output_name:
-#         (optional) string, name of the generated plist file, default to
-#         "$target_gen_dir/$target_name.plist".
-template("apple_info_plist") {
-  assert(defined(invoker.executable_name),
-         "The executable_name must be specified for $target_name")
-  executable_name = invoker.executable_name
-
-  compile_plist(target_name) {
-    forward_variables_from(invoker,
-                           [
-                             "plist_templates",
-                             "testonly",
-                             "deps",
-                             "visibility",
-                             "format",
-                           ])
-
-    if (defined(invoker.output_name)) {
-      output_name = invoker.output_name
-    } else {
-      output_name = "$target_gen_dir/$target_name.plist"
-    }
-
-    substitutions = [
-      "EXECUTABLE_NAME=$executable_name",
-      "GCC_VERSION=com.apple.compilers.llvm.clang.1_0",
-      "PRODUCT_NAME=$executable_name",
-    ]
-    if (defined(invoker.extra_substitutions)) {
-      substitutions += invoker.extra_substitutions
-    }
-  }
-}
diff --git a/build/apple/compile_entitlements.gni b/build/apple/compile_entitlements.gni
deleted file mode 100644
index 006d5ac..0000000
--- a/build/apple/compile_entitlements.gni
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/apple/compile_plist.gni")
-
-# Template to merge multiple .entitlements files performing variable
-# substitutions.
-#
-# Arguments
-#
-#     entitlements_templates:
-#         string array, paths to entitlements files which will be used for the
-#         bundle.
-#
-#     substitutions:
-#         string array, 'key=value' pairs used to replace ${key} by value
-#         when generating the output plist file.
-#
-#     output_name:
-#         string, name of the generated entitlements file.
-template("compile_entitlements") {
-  assert(defined(invoker.entitlements_templates),
-         "A list of template plist files must be specified for $target_name")
-
-  compile_plist(target_name) {
-    forward_variables_from(invoker,
-                           "*",
-                           [
-                             "entitlements_templates",
-                             "format",
-                             "plist_templates",
-                           ])
-
-    plist_templates = invoker.entitlements_templates
-
-    # Entitlements files are always encoded in xml1.
-    format = "xml1"
-
-    # Entitlements files use unsubstitued variables, so define substitutions
-    # to leave those variables untouched.
-    if (!defined(substitutions)) {
-      substitutions = []
-    }
-
-    substitutions += [
-      "AppIdentifierPrefix=\$(AppIdentifierPrefix)",
-      "CFBundleIdentifier=\$(CFBundleIdentifier)",
-    ]
-  }
-}
diff --git a/build/apple/compile_plist.gni b/build/apple/compile_plist.gni
deleted file mode 100644
index 682e00f..0000000
--- a/build/apple/compile_plist.gni
+++ /dev/null
@@ -1,76 +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.
-
-# Template to merge multiple plist files and perform variable substitutions.
-#
-# Arguments
-#
-#     plist_templates:
-#         string array, paths to plist files which will be used for the bundle.
-#
-#     format:
-#         string, the format to `plutil -convert` the plist to when
-#         generating the output.
-#
-#     substitutions:
-#         string array, 'key=value' pairs used to replace ${key} by value
-#         when generating the output plist file.
-#
-#     output_name:
-#         string, name of the generated plist file.
-template("compile_plist") {
-  assert(defined(invoker.plist_templates),
-         "A list of template plist files must be specified for $target_name")
-  assert(defined(invoker.format),
-         "The plist format must be specified for $target_name")
-  assert(defined(invoker.substitutions),
-         "A list of key=value pairs must be specified for $target_name")
-  assert(defined(invoker.output_name),
-         "The name of the output file must be specified for $target_name")
-
-  _output_name = invoker.output_name
-  _merged_name = get_path_info(_output_name, "dir") + "/" +
-                 get_path_info(_output_name, "name") + "_merged." +
-                 get_path_info(_output_name, "extension")
-
-  _merge_target = target_name + "_merge"
-
-  action(_merge_target) {
-    forward_variables_from(invoker,
-                           [
-                             "deps",
-                             "testonly",
-                           ])
-
-    script = "//build/config/mac/plist_util.py"
-    sources = invoker.plist_templates
-    outputs = [ _merged_name ]
-    args = [
-             "merge",
-             "-f=" + invoker.format,
-             "-o=" + rebase_path(_merged_name, root_build_dir),
-           ] + rebase_path(invoker.plist_templates, root_build_dir)
-  }
-
-  action(target_name) {
-    forward_variables_from(invoker,
-                           [
-                             "testonly",
-                             "visibility",
-                           ])
-    script = "//build/config/mac/plist_util.py"
-    sources = [ _merged_name ]
-    outputs = [ _output_name ]
-    args = [
-      "substitute",
-      "-f=" + invoker.format,
-      "-o=" + rebase_path(_output_name, root_build_dir),
-      "-t=" + rebase_path(_merged_name, root_build_dir),
-    ]
-    foreach(_substitution, invoker.substitutions) {
-      args += [ "-s=$_substitution" ]
-    }
-    deps = [ ":$_merge_target" ]
-  }
-}
diff --git a/build/apple/convert_plist.gni b/build/apple/convert_plist.gni
deleted file mode 100644
index fae7526d..0000000
--- a/build/apple/convert_plist.gni
+++ /dev/null
@@ -1,41 +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.
-
-# Convert plist file to given format.
-#
-# Arguments
-#
-#   source:
-#     string, path to the plist file to convert
-#
-#   output:
-#     string, path to the converted plist, must be under $root_build_dir
-#
-#   format:
-#     string, the format to convert the plist to. Either "binary1" or "xml1".
-template("convert_plist") {
-  assert(defined(invoker.source), "source must be defined for $target_name")
-  assert(defined(invoker.output), "output must be defined for $target_name")
-  assert(defined(invoker.format), "format must be defined for $target_name")
-
-  action(target_name) {
-    forward_variables_from(invoker,
-                           [
-                             "visibility",
-                             "testonly",
-                             "deps",
-                           ])
-
-    script = "//build/config/mac/plist_util.py"
-    sources = [ invoker.source ]
-    outputs = [ invoker.output ]
-    args = [
-      "merge",
-      "--format=${invoker.format}",
-      "-o",
-      rebase_path(invoker.output, root_build_dir),
-      rebase_path(invoker.source, root_build_dir),
-    ]
-  }
-}
diff --git a/build/apple/tweak_info_plist.py b/build/apple/tweak_info_plist.py
index 305a53f..249fb0c 100755
--- a/build/apple/tweak_info_plist.py
+++ b/build/apple/tweak_info_plist.py
@@ -414,4 +414,8 @@
 
 
 if __name__ == '__main__':
+  # TODO(https://crbug.com/941669): Temporary workaround until all scripts use
+  # python3 by default.
+  if sys.version_info[0] < 3:
+    os.execvp('python3', ['python3'] + sys.argv)
   sys.exit(Main(sys.argv[1:]))
diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn
index e64e056..4bedf1ef 100644
--- a/build/config/BUILDCONFIG.gn
+++ b/build/config/BUILDCONFIG.gn
@@ -234,7 +234,8 @@
 } else if (target_os == "ios") {
   _default_toolchain = "//build/toolchain/mac:ios_clang_$target_cpu"
 } else if (target_os == "mac") {
-  assert(host_os == "mac", "Mac cross-compiles are unsupported.")
+  assert(host_os == "mac" || host_os == "linux",
+         "Mac cross-compiles are unsupported.")
   _default_toolchain = "//build/toolchain/mac:clang_$target_cpu"
 } else if (target_os == "win") {
   # On Windows, we use the same toolchain for host and target by default.
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index c6c1c2b4..0eb315b 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -288,11 +288,8 @@
   # Don't emit the GCC version ident directives, they just end up in the
   # .comment section or debug info taking up binary size, and makes comparing
   # .o files built with different compiler versions harder.
-  if (!is_win) {
+  if (!is_win || is_clang) {
     cflags += [ "-fno-ident" ]
-  } else if (is_clang) {
-    # TODO(crbug.com/1183366): Use -fno-ident once clang-cl supports it.
-    cflags += [ "/clang:-fno-ident" ]
   }
 
   # In general, Windows is totally different, but all the other builds share
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index 10262d53..96a18d9 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -195,8 +195,9 @@
 declare_args() {
   # Set to true to use lld, the LLVM linker.
   # Not supported for macOS (see docs/mac_lld.md), and not functional at all for
-  # iOS. The default linker everywhere else.
-  use_lld = is_clang && !is_apple
+  # iOS. But used for mac cross-compile on linux (may not work properly).
+  # The default linker everywhere else.
+  use_lld = is_clang && (!is_apple || host_os == "linux")
 }
 
 declare_args() {
diff --git a/build/config/fuchsia/test/font_capabilities.test-cmx b/build/config/fuchsia/test/font_capabilities.test-cmx
new file mode 100644
index 0000000..4c8661bb
--- /dev/null
+++ b/build/config/fuchsia/test/font_capabilities.test-cmx
@@ -0,0 +1,14 @@
+{
+  "facets": {
+    "fuchsia.test": {
+      "injected-services": {
+        "fuchsia.fonts.Provider": "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx",
+      }
+    }
+  },
+  "sandbox": {
+    "services": [
+      "fuchsia.fonts.Provider"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/build/config/fuchsia/test/present_capabilities.test-cmx b/build/config/fuchsia/test/present_capabilities.test-cmx
index 849fe70..0600aca 100644
--- a/build/config/fuchsia/test/present_capabilities.test-cmx
+++ b/build/config/fuchsia/test/present_capabilities.test-cmx
@@ -1,10 +1,9 @@
 {
   "facets": {
     "fuchsia.test": {
-      "injected-services": {
-        "fuchsia.ui.policy.Presenter": "fuchsia-pkg://fuchsia.com/root_presenter#meta/root_presenter.cmx"
-      },
-
+      "system-services": [
+        "fuchsia.ui.policy.Presenter"
+      ]
     }
   },
   "sandbox": {
diff --git a/build/config/fuchsia/test/view_creation_capabilities.test-cmx b/build/config/fuchsia/test/view_creation_capabilities.test-cmx
index 4bb438a7..ba49aaa 100644
--- a/build/config/fuchsia/test/view_creation_capabilities.test-cmx
+++ b/build/config/fuchsia/test/view_creation_capabilities.test-cmx
@@ -3,12 +3,13 @@
     "fuchsia.test": {
       "injected-services": {
         "fuchsia.accessibility.semantics.SemanticsManager": "fuchsia-pkg://fuchsia.com/a11y-manager#meta/a11y-manager.cmx",
-        "fuchsia.sysmem.Allocator": "fuchsia-pkg://fuchsia.com/sysmem_connector#meta/sysmem_connector.cmx",
         "fuchsia.ui.input.ImeService": "fuchsia-pkg://fuchsia.com/ime_service#meta/ime_service.cmx",
         "fuchsia.ui.input.ImeVisibilityService": "fuchsia-pkg://fuchsia.com/ime_service#meta/ime_service.cmx",
-        "fuchsia.ui.scenic.Scenic": "fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cmx"
       },
-
+      "system-services": [
+        "fuchsia.sysmem.Allocator",
+        "fuchsia.ui.scenic.Scenic"
+      ]
     }
   },
   "sandbox": {
diff --git a/build/config/fuchsia/test/vulkan_capabilities.test-cmx b/build/config/fuchsia/test/vulkan_capabilities.test-cmx
new file mode 100644
index 0000000..0436ffd
--- /dev/null
+++ b/build/config/fuchsia/test/vulkan_capabilities.test-cmx
@@ -0,0 +1,19 @@
+{
+  "facets": {
+    "fuchsia.test": {
+      "system-services": [
+        "fuchsia.sysmem.Allocator",
+        "fuchsia.vulkan.loader.Loader"
+      ]
+    }
+  },
+  "sandbox": {
+    "features": [
+      "vulkan"
+    ],
+    "services": [
+      "fuchsia.sysmem.Allocator",
+      "fuchsia.vulkan.loader.Loader"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/build/config/ios/rules.gni b/build/config/ios/rules.gni
index 7949203..ecc46a24 100644
--- a/build/config/ios/rules.gni
+++ b/build/config/ios/rules.gni
@@ -2,9 +2,9 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/apple/apple_info_plist.gni")
 import("//build/config/apple/symbols.gni")
 import("//build/config/ios/ios_sdk.gni")
+import("//build/config/mac/base_rules.gni")
 import("//build/toolchain/goma.gni")
 import("//build/toolchain/toolchain.gni")
 import("//build_overrides/build.gni")
@@ -523,7 +523,7 @@
     _info_plist = _info_plist_target_output[0]
   }
 
-  apple_info_plist(target_name) {
+  info_plist(target_name) {
     format = "binary1"
     extra_substitutions = []
     if (defined(invoker.extra_substitutions)) {
@@ -537,10 +537,6 @@
       "IOS_SDK_BUILD=$ios_sdk_build",
       "IOS_SDK_NAME=$ios_sdk_name$ios_sdk_version",
       "IOS_SUPPORTED_PLATFORM=$ios_sdk_platform",
-      "BUILD_MACHINE_OS_BUILD=$machine_os_build",
-      "IOS_DEPLOYMENT_TARGET=$ios_deployment_target",
-      "XCODE_BUILD=$xcode_build",
-      "XCODE_VERSION=$xcode_version",
     ]
     plist_templates = [
       "//build/config/ios/BuildInfo.plist",
diff --git a/build/config/locales.gni b/build/config/locales.gni
index 5daf12e..754a0ef7 100644
--- a/build/config/locales.gni
+++ b/build/config/locales.gni
@@ -7,8 +7,11 @@
 # This file creates the |locales| which is the set of current
 # locales based on the current platform. Locales in this list are formated
 # based on what .pak files expect.
-# The |locales_with_fake_bidi| variable is the same but with
-# the fake bidirectional locale added.
+# The |locales| variable *may* contain pseudolocales, depending on the
+# |enable_pseudolocales| flag.
+# If you specifically want to have the locales variable with or without
+# pseudolocales, then use |locales_with_pseudolocales| or
+# |locales_without_pseudolocales|.
 
 # The following additional platform specific lists are created:
 # - |android_apk_locales| subset for Android based apk builds
@@ -125,7 +128,7 @@
   "sl",
   "sq",
   "sr",
-  "sr-Latn", # -b+sr+Latn in .xml
+  "sr-Latn",  # -b+sr+Latn in .xml
   "sv",
   "sw",
   "ta",
@@ -200,14 +203,14 @@
   android_bundle_locales_as_resources -= [
     "en-rUS",
     "es-r419",
-    "sr-rLatn",
     "fil",
     "he",
     "id",
+    "sr-rLatn",
   ]
   android_bundle_locales_as_resources += [
-    "es-rUS",
     "b+sr+Latn",
+    "es-rUS",
     "in",
     "iw",
     "tl",
@@ -234,10 +237,12 @@
   ]
 }
 
-# This doesn't actually enable the locale - it just allows it to compile.
-# In order to enable it, it needs to be in the locales variable.
-# A future CL will move it there for dev builds only.
-locales_with_fake_bidi = locales + [ "ar-XB", "en-XA" ]
+pseudolocales = [
+  "ar-XB",
+  "en-XA",
+]
+locales_without_pseudolocales = locales
+locales_with_pseudolocales = locales + pseudolocales
 
 # Same as the locales list but in the format Mac expects for output files:
 # it uses underscores instead of hyphens, and "en" instead of "en-US".
diff --git a/build/config/mac/base_rules.gni b/build/config/mac/base_rules.gni
new file mode 100644
index 0000000..28c24802c
--- /dev/null
+++ b/build/config/mac/base_rules.gni
@@ -0,0 +1,239 @@
+# 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.
+
+# This file contains rules that are shared between Mac and iOS.
+
+import("//build/config/apple/symbols.gni")
+import("//build/toolchain/toolchain.gni")
+
+if (is_mac) {
+  import("//build/config/mac/mac_sdk.gni")
+} else if (is_ios) {
+  import("//build/config/ios/ios_sdk.gni")
+}
+
+# Convert plist file to given format.
+#
+# Arguments
+#
+#   source:
+#     string, path to the plist file to convert
+#
+#   output:
+#     string, path to the converted plist, must be under $root_build_dir
+#
+#   format:
+#     string, the format to convert the plist to. Either "binary1" or "xml1".
+template("convert_plist") {
+  assert(defined(invoker.source), "source must be defined for $target_name")
+  assert(defined(invoker.output), "output must be defined for $target_name")
+  assert(defined(invoker.format), "format must be defined for $target_name")
+
+  action(target_name) {
+    forward_variables_from(invoker,
+                           [
+                             "visibility",
+                             "testonly",
+                             "deps",
+                           ])
+
+    script = "//build/config/mac/plist_util.py"
+    sources = [ invoker.source ]
+    outputs = [ invoker.output ]
+    args = [
+      "merge",
+      "--format=${invoker.format}",
+      "-o",
+      rebase_path(invoker.output, root_build_dir),
+      rebase_path(invoker.source, root_build_dir),
+    ]
+  }
+}
+
+# Template to merge multiple plist files and perform variable substitutions.
+#
+# Arguments
+#
+#     plist_templates:
+#         string array, paths to plist files which will be used for the bundle.
+#
+#     format:
+#         string, the format to `plutil -convert` the plist to when
+#         generating the output.
+#
+#     substitutions:
+#         string array, 'key=value' pairs used to replace ${key} by value
+#         when generating the output plist file.
+#
+#     output_name:
+#         string, name of the generated plist file.
+template("compile_plist") {
+  assert(defined(invoker.plist_templates),
+         "A list of template plist files must be specified for $target_name")
+  assert(defined(invoker.format),
+         "The plist format must be specified for $target_name")
+  assert(defined(invoker.substitutions),
+         "A list of key=value pairs must be specified for $target_name")
+  assert(defined(invoker.output_name),
+         "The name of the output file must be specified for $target_name")
+
+  _output_name = invoker.output_name
+  _merged_name = get_path_info(_output_name, "dir") + "/" +
+                 get_path_info(_output_name, "name") + "_merged." +
+                 get_path_info(_output_name, "extension")
+
+  _merge_target = target_name + "_merge"
+
+  action(_merge_target) {
+    forward_variables_from(invoker,
+                           [
+                             "deps",
+                             "testonly",
+                           ])
+
+    script = "//build/config/mac/plist_util.py"
+    sources = invoker.plist_templates
+    outputs = [ _merged_name ]
+    args = [
+             "merge",
+             "-f=" + invoker.format,
+             "-o=" + rebase_path(_merged_name, root_build_dir),
+           ] + rebase_path(invoker.plist_templates, root_build_dir)
+  }
+
+  action(target_name) {
+    forward_variables_from(invoker,
+                           [
+                             "testonly",
+                             "visibility",
+                           ])
+    script = "//build/config/mac/plist_util.py"
+    sources = [ _merged_name ]
+    outputs = [ _output_name ]
+    args = [
+      "substitute",
+      "-f=" + invoker.format,
+      "-o=" + rebase_path(_output_name, root_build_dir),
+      "-t=" + rebase_path(_merged_name, root_build_dir),
+    ]
+    foreach(_substitution, invoker.substitutions) {
+      args += [ "-s=$_substitution" ]
+    }
+    deps = [ ":$_merge_target" ]
+  }
+}
+
+# Template to merge multiple .entitlements files performing variable
+# substitutions.
+#
+# Arguments
+#
+#     entitlements_templates:
+#         string array, paths to entitlements files which will be used for the
+#         bundle.
+#
+#     substitutions:
+#         string array, 'key=value' pairs used to replace ${key} by value
+#         when generating the output plist file.
+#
+#     output_name:
+#         string, name of the generated entitlements file.
+template("compile_entitlements") {
+  assert(defined(invoker.entitlements_templates),
+         "A list of template plist files must be specified for $target_name")
+
+  compile_plist(target_name) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "entitlements_templates",
+                             "format",
+                             "plist_templates",
+                           ])
+
+    plist_templates = invoker.entitlements_templates
+
+    # Entitlements files are always encoded in xml1.
+    format = "xml1"
+
+    # Entitlements files use unsubstitued variables, so define substitutions
+    # to leave those variables untouched.
+    if (!defined(substitutions)) {
+      substitutions = []
+    }
+
+    substitutions += [
+      "AppIdentifierPrefix=\$(AppIdentifierPrefix)",
+      "CFBundleIdentifier=\$(CFBundleIdentifier)",
+    ]
+  }
+}
+
+# The base template used to generate Info.plist files for iOS and Mac apps and
+# frameworks.
+#
+# Arguments
+#
+#     plist_templates:
+#         string array, paths to plist files which will be used for the bundle.
+#
+#     executable_name:
+#         string, name of the generated target used for the product
+#         and executable name as specified in the output Info.plist.
+#
+#     format:
+#         string, the format to `plutil -convert` the plist to when
+#         generating the output.
+#
+#     extra_substitutions:
+#         (optional) string array, 'key=value' pairs for extra fields which are
+#         specified in a source Info.plist template.
+#
+#     output_name:
+#         (optional) string, name of the generated plist file, default to
+#         "$target_gen_dir/$target_name.plist".
+template("info_plist") {
+  assert(defined(invoker.executable_name),
+         "The executable_name must be specified for $target_name")
+  executable_name = invoker.executable_name
+
+  compile_plist(target_name) {
+    forward_variables_from(invoker,
+                           [
+                             "plist_templates",
+                             "testonly",
+                             "deps",
+                             "visibility",
+                             "format",
+                           ])
+
+    if (defined(invoker.output_name)) {
+      output_name = invoker.output_name
+    } else {
+      output_name = "$target_gen_dir/$target_name.plist"
+    }
+
+    substitutions = [
+      "EXECUTABLE_NAME=$executable_name",
+      "GCC_VERSION=com.apple.compilers.llvm.clang.1_0",
+      "PRODUCT_NAME=$executable_name",
+      "XCODE_BUILD=$xcode_build",
+      "XCODE_VERSION=$xcode_version",
+    ]
+    if (is_mac) {
+      substitutions += [
+        "MACOSX_DEPLOYMENT_TARGET=$mac_deployment_target",
+        "CHROMIUM_MIN_SYSTEM_VERSION=$mac_min_system_version",
+      ]
+    } else if (is_ios) {
+      substitutions += [
+        "BUILD_MACHINE_OS_BUILD=$machine_os_build",
+        "IOS_DEPLOYMENT_TARGET=$ios_deployment_target",
+      ]
+    }
+    if (defined(invoker.extra_substitutions)) {
+      substitutions += invoker.extra_substitutions
+    }
+  }
+}
diff --git a/build/config/mac/plist_util.py b/build/config/mac/plist_util.py
index 4517788..ad85f15c5 100644
--- a/build/config/mac/plist_util.py
+++ b/build/config/mac/plist_util.py
@@ -249,4 +249,8 @@
 
 
 if __name__ == '__main__':
+  # TODO(https://crbug.com/941669): Temporary workaround until all scripts use
+  # python3 by default.
+  if sys.version_info[0] < 3:
+    os.execvp('python3', ['python3'] + sys.argv)
   sys.exit(Main())
diff --git a/build/config/mac/rules.gni b/build/config/mac/rules.gni
index 1ec96d8..e741a7a6b 100644
--- a/build/config/mac/rules.gni
+++ b/build/config/mac/rules.gni
@@ -2,9 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/apple/apple_info_plist.gni")
-import("//build/config/apple/symbols.gni")
-import("//build/config/mac/mac_sdk.gni")
+import("//build/config/mac/base_rules.gni")
 
 # Generates Info.plist files for Mac apps and frameworks.
 #
@@ -38,7 +36,7 @@
     _info_plist = _info_plist_target_output[0]
   }
 
-  apple_info_plist(target_name) {
+  info_plist(target_name) {
     format = "xml1"
     extra_substitutions = []
     if (defined(invoker.extra_substitutions)) {
@@ -47,10 +45,6 @@
     extra_substitutions += [
       "MAC_SDK_BUILD=$mac_sdk_version",
       "MAC_SDK_NAME=$mac_sdk_name$mac_sdk_version",
-      "MACOSX_DEPLOYMENT_TARGET=$mac_deployment_target",
-      "CHROMIUM_MIN_SYSTEM_VERSION=$mac_min_system_version",
-      "XCODE_BUILD=$xcode_build",
-      "XCODE_VERSION=$xcode_version",
     ]
     plist_templates = [
       "//build/config/mac/BuildInfo.plist",
diff --git a/build/config/mac/write_pkg_info.py b/build/config/mac/write_pkg_info.py
index 67204ab..a64d7f7 100644
--- a/build/config/mac/write_pkg_info.py
+++ b/build/config/mac/write_pkg_info.py
@@ -44,4 +44,8 @@
 
 
 if __name__ == '__main__':
+  # TODO(https://crbug.com/941669): Temporary workaround until all scripts use
+  # python3 by default.
+  if sys.version_info[0] < 3:
+    os.execvp('python3', ['python3'] + sys.argv)
   sys.exit(Main())
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 052093ba..cd5c9267 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-3.20210310.0.1
+3.20210310.2.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 052093ba..cd5c9267 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-3.20210310.0.1
+3.20210310.2.1
diff --git a/build/print_python_deps.py b/build/print_python_deps.py
index ab00d0c..d2a6cfcc 100755
--- a/build/print_python_deps.py
+++ b/build/print_python_deps.py
@@ -58,8 +58,8 @@
     args.extend(('--output', os.path.relpath(options.output, _SRC_ROOT)))
   if options.gn_paths:
     args.extend(('--gn-paths',))
-  for whitelist in sorted(options.whitelists):
-    args.extend(('--whitelist', os.path.relpath(whitelist, _SRC_ROOT)))
+  for allowlist in sorted(options.allowlists):
+    args.extend(('--allowlist', os.path.relpath(allowlist, _SRC_ROOT)))
   args.append(os.path.relpath(options.module, _SRC_ROOT))
   return ' '.join(pipes.quote(x) for x in args)
 
@@ -136,8 +136,10 @@
                       help='Write paths as //foo/bar/baz.py')
   parser.add_argument('--did-relaunch', action='store_true',
                       help=argparse.SUPPRESS)
-  parser.add_argument('--whitelist', default=[], action='append',
-                      dest='whitelists',
+  parser.add_argument('--allowlist',
+                      default=[],
+                      action='append',
+                      dest='allowlists',
                       help='Recursively include all non-test python files '
                       'within this directory. May be specified multiple times.')
   options = parser.parse_args()
@@ -193,7 +195,7 @@
     sys.stderr.write('python={}\n'.format(sys.executable))
     raise
 
-  for path in options.whitelists:
+  for path in options.allowlists:
     paths_set.update(
         os.path.abspath(p)
         for p in _FindPythonInDirectory(path, allow_test=False))
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 3465bf70..848f606e 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -860,7 +860,14 @@
   ]
 
   if (is_fuchsia) {
-    manifest = "//build/config/fuchsia/gfx_tests.cmx"
+    additional_manifests = [
+      "//build/config/fuchsia/test/font_capabilities.test-cmx",
+
+      # TODO(crbug.com/1185811): Figure out why jit_capabilities is needed.
+      "//build/config/fuchsia/test/jit_capabilities.test-cmx",
+
+      "//build/config/fuchsia/test/vulkan_capabilities.test-cmx",
+    ]
   }
 
   if (enable_vulkan) {
diff --git a/cc/base/features.cc b/cc/base/features.cc
index 1fa75754..bab5f6ca 100644
--- a/cc/base/features.cc
+++ b/cc/base/features.cc
@@ -22,7 +22,12 @@
 
 // Enables impulse-style scroll animations in place of the default ones.
 const base::Feature kImpulseScrollAnimations = {
-    "ImpulseScrollAnimations", base::FEATURE_ENABLED_BY_DEFAULT};
+    "ImpulseScrollAnimations",
+#if defined(OS_MAC)
+    base::FEATURE_DISABLED_BY_DEFAULT};
+#else
+    base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
 
 // Whether the compositor should attempt to sync with the scroll handlers before
 // submitting a frame.
diff --git a/cc/paint/paint_flags.cc b/cc/paint/paint_flags.cc
index 62f41a0..a55e680 100644
--- a/cc/paint/paint_flags.cc
+++ b/cc/paint/paint_flags.cc
@@ -11,7 +11,7 @@
 namespace {
 
 static bool affects_alpha(const SkColorFilter* cf) {
-  return cf && !(cf->getFlags() & SkColorFilter::kAlphaUnchanged_Flag);
+  return cf && !cf->isAlphaUnchanged();
 }
 
 }  // namespace
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index f8085398..1fc46fc 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -36,11 +36,10 @@
   import("//build/linux/extract_symbols.gni")
   import("//build/linux/strip_binary.gni")
 } else if (is_mac) {
-  import("//build/apple/compile_entitlements.gni")
-  import("//build/apple/compile_plist.gni")
   import("//build/apple/tweak_info_plist.gni")
   import("//build/compiled_action.gni")
   import("//build/config/apple/symbols.gni")
+  import("//build/config/mac/base_rules.gni")
   import("//build/config/mac/mac_sdk.gni")
   import("//build/config/mac/rules.gni")
   import("//build/util/branding.gni")
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 8dca5f0..696be0f 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -307,6 +307,7 @@
     "//chrome/browser/android/webapps/launchpad:java",
     "//chrome/browser/banners/android:java",
     "//chrome/browser/browser_controls/android:java",
+    "//chrome/browser/commerce/subscriptions/android:java",
     "//chrome/browser/consent_auditor/android:java",
     "//chrome/browser/contextmenu:java",
     "//chrome/browser/continuous_search:java",
@@ -663,6 +664,7 @@
   deps = [
     ":chrome_java",
     "//chrome/android/features/keyboard_accessory:internal_java",
+    "//chrome/browser/commerce/subscriptions/android:java",
     "//chrome/browser/download/internal/android:java",
     "//chrome/browser/page_annotations/android:java",
     "//chrome/browser/password_check:internal_java",
@@ -835,6 +837,7 @@
     "//chrome/browser/banners/android:java",
     "//chrome/browser/browser_controls/android:java",
     "//chrome/browser/browser_controls/android:junit",
+    "//chrome/browser/commerce/subscriptions/test/android:junit",
     "//chrome/browser/contextmenu:java",
     "//chrome/browser/continuous_search:junit",
     "//chrome/browser/continuous_search/internal:junit",
@@ -3525,7 +3528,6 @@
     "java/src/org/chromium/chrome/browser/webapps/WebappRegistry.java",
     "java/src/org/chromium/chrome/browser/webauth/AuthenticatorImpl.java",
     "java/src/org/chromium/chrome/browser/webauth/Fido2Helper.java",
-    "java/src/org/chromium/chrome/browser/webauth/IsUvpaaHelper.java",
   ]
 
   # Used for testing only, should not be shipped to end users.
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index bdfb503..35f1c33 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -611,7 +611,6 @@
   "java/res/drawable/ic_photo_camera_black.xml",
   "java/res/drawable/ic_reading_list_folder.xml",
   "java/res/drawable/ic_settings_black.xml",
-  "java/res/drawable/ic_share_small.xml",
   "java/res/drawable/ic_signal_cellular_0_bar.xml",
   "java/res/drawable/ic_signal_cellular_1_bar.xml",
   "java/res/drawable/ic_signal_cellular_2_bar.xml",
@@ -875,7 +874,6 @@
   "java/res/menu/custom_tabs_menu.xml",
   "java/res/menu/history_manager_menu.xml",
   "java/res/menu/main_menu.xml",
-  "java/res/menu/main_menu_regroup.xml",
   "java/res/menu/password_entry_viewer_action_bar_menu.xml",
   "java/res/menu/prefeditor_editor_menu.xml",
   "java/res/menu/save_password_preferences_action_bar_menu.xml",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 85c54ca..e3498b9 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1449,7 +1449,6 @@
   "java/src/org/chromium/chrome/browser/webauth/Fido2Helper.java",
   "java/src/org/chromium/chrome/browser/webauth/FidoErrorResponseCallback.java",
   "java/src/org/chromium/chrome/browser/webauth/GetAssertionResponseCallback.java",
-  "java/src/org/chromium/chrome/browser/webauth/IsUvpaaHelper.java",
   "java/src/org/chromium/chrome/browser/webauth/IsUvpaaResponseCallback.java",
   "java/src/org/chromium/chrome/browser/webauth/MakeCredentialResponseCallback.java",
   "java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticatorActivity.java",
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
index 31549a2..973b9c5 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
@@ -14,6 +14,7 @@
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withParent;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -45,6 +46,7 @@
 import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.espresso.UiController;
 import androidx.test.espresso.ViewAction;
@@ -531,7 +533,7 @@
         // Note that onView(R.id.more_tabs).perform(click()) can not be used since it requires 90
         // percent of the view's area is displayed to the users. However, this view has negative
         // margin which makes the percentage is less than 90.
-        // TODO(crbug.com/1025296): Investigate whether this would be a problem for real users.
+        // TODO(crbug.com/1186752): Investigate whether this would be a problem for real users.
         try {
             TestThreadUtils.runOnUiThreadBlocking(()
                                                           -> mActivityTestRule.getActivity()
@@ -1303,6 +1305,50 @@
 
     @Test
     @MediumTest
+    // clang-format off
+    @EnableFeatures({ChromeFeatureList.TAB_SWITCHER_ON_RETURN + "<Study,",
+            ChromeFeatureList.START_SURFACE_ANDROID + "<Study"})
+    @CommandLineFlags.Add({ChromeSwitches.DISABLE_NATIVE_INITIALIZATION,
+            "force-fieldtrials=Study/Group",
+            IMMEDIATE_RETURN_PARAMS + "/start_surface_variation/single"})
+    public void testScrollToSelectedTab() throws IOException {
+        // clang-format on
+        createTabStateFile(new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, null, 9);
+        startMainActivityFromLauncher();
+        CriteriaHelper.pollUiThread(
+                mActivityTestRule.getActivity().getLayoutManager()::overviewVisible);
+        startAndWaitNativeInitialization();
+        CriteriaHelper.pollUiThread(
+                () -> mActivityTestRule.getActivity().getLayoutManager().overviewVisible());
+
+        // Note that onView(R.id.more_tabs).perform(click()) can not be used since it requires 90
+        // percent of the view's area is displayed to the users. However, this view has negative
+        // margin which makes the percentage is less than 90.
+        // TODO(crbug.com/1186752): Investigate whether this would be a problem for real users.
+        try {
+            TestThreadUtils.runOnUiThreadBlocking(
+                    ()
+                            -> mActivityTestRule.getActivity()
+                                       .findViewById(org.chromium.chrome.tab_ui.R.id.more_tabs)
+                                       .performClick());
+        } catch (ExecutionException e) {
+            Assert.fail("Failed to tap 'more tabs' " + e.toString());
+        }
+        onViewWaiting(withId(R.id.secondary_tasks_surface_view));
+
+        // Make sure the grid tab switcher is scrolled down to show the selected tab.
+        onView(allOf(withId(R.id.tab_list_view), withParent(withId(R.id.tasks_surface_body))))
+                .check((v, noMatchException) -> {
+                    if (noMatchException != null) throw noMatchException;
+                    Assert.assertTrue(v instanceof RecyclerView);
+                    LinearLayoutManager layoutManager =
+                            (LinearLayoutManager) ((RecyclerView) v).getLayoutManager();
+                    assertEquals(9, layoutManager.findLastVisibleItemPosition());
+                });
+    }
+
+    @Test
+    @MediumTest
     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
     // clang-format off
     @EnableFeatures({ChromeFeatureList.TAB_SWITCHER_ON_RETURN + "<Study,",
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index b789bbc3..f4f35162 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -440,7 +440,7 @@
         // Note that onView(R.id.more_tabs).perform(click()) can not be used since it requires 90
         // percent of the view's area is displayed to the users. However, this view has negative
         // margin which makes the percentage is less than 90.
-        // TODO(crbug.com/1025296): Investigate whether this would be a problem for real users.
+        // TODO(crbug.com/1186752): Investigate whether this would be a problem for real users.
         try {
             TestThreadUtils.runOnUiThreadBlocking(
                     ()
@@ -510,7 +510,7 @@
         // Note that onView(R.id.more_tabs).perform(click()) can not be used since it requires 90
         // percent of the view's area is displayed to the users. However, this view has negative
         // margin which makes the percentage is less than 90.
-        // TODO(crbug.com/1025296): Investigate whether this would be a problem for real users.
+        // TODO(crbug.com/1186752): Investigate whether this would be a problem for real users.
         try {
             TestThreadUtils.runOnUiThreadBlocking(
                     ()
@@ -587,7 +587,7 @@
         // Note that onView(R.id.more_tabs).perform(click()) can not be used since it requires 90
         // percent of the view's area is displayed to the users. However, this view has negative
         // margin which makes the percentage is less than 90.
-        // TODO(crbug.com/1025296): Investigate whether this would be a problem for real users.
+        // TODO(crbug.com/1186752): Investigate whether this would be a problem for real users.
         try {
             TestThreadUtils.runOnUiThreadBlocking(
                     ()
@@ -661,7 +661,7 @@
         // Note that onView(R.id.more_tabs).perform(click()) can not be used since it requires 90
         // percent of the view's area is displayed to the users. However, this view has negative
         // margin which makes the percentage is less than 90.
-        // TODO(crbug.com/1025296): Investigate whether this would be a problem for real users.
+        // TODO(crbug.com/1186752): Investigate whether this would be a problem for real users.
         try {
             TestThreadUtils.runOnUiThreadBlocking(
                     ()
@@ -1856,7 +1856,7 @@
         // Note that onView(R.id.more_tabs).perform(click()) can not be used since it requires 90
         // percent of the view's area is displayed to the users. However, this view has negative
         // margin which makes the percentage is less than 90.
-        // TODO(crbug.com/1025296): Investigate whether this would be a problem for real users.
+        // TODO(crbug.com/1186752): Investigate whether this would be a problem for real users.
         try {
             TestThreadUtils.runOnUiThreadBlocking(
                     ()
diff --git a/chrome/android/features/tab_ui/java/res/layout/price_tracking_dialog_layout.xml b/chrome/android/features/tab_ui/java/res/layout/price_tracking_dialog_layout.xml
index 8fae323..9319c74 100644
--- a/chrome/android/features/tab_ui/java/res/layout/price_tracking_dialog_layout.xml
+++ b/chrome/android/features/tab_ui/java/res/layout/price_tracking_dialog_layout.xml
@@ -103,11 +103,17 @@
                 android:text="@string/price_drop_alerts_description"
                 android:textAppearance="@style/TextAppearance.TextMedium.Secondary" />
         </LinearLayout>
-        <androidx.appcompat.widget.SwitchCompat
-            android:id="@+id/price_alerts_switch"
+        <org.chromium.ui.widget.ChromeImageButton
+            android:id="@+id/price_alerts_arrow"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="3"
-            android:layout_gravity="center" />
+            android:paddingStart="@dimen/price_tracking_dialog_text_side_margin"
+            android:paddingEnd="0dp"
+            android:layout_gravity="center"
+            android:contentDescription="@string/price_drop_alerts_card_go_to_settings"
+            android:src="@drawable/ic_expand_more_horizontal_black_24dp"
+            android:background="@drawable/tab_grid_dialog_background"
+            app:tint="@color/default_icon_color_tint_list" />
     </LinearLayout>
 </org.chromium.chrome.browser.tasks.tab_management.PriceTrackingDialogView>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogCoordinator.java
index e4063a96..67ea30e 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogCoordinator.java
@@ -9,6 +9,7 @@
 import android.widget.CompoundButton;
 import android.widget.CompoundButton.OnCheckedChangeListener;
 
+import org.chromium.chrome.browser.price_tracking.PriceDropNotificationManager;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.modaldialog.ModalDialogManager;
@@ -24,10 +25,13 @@
     private final PriceTrackingDialogView mDialogView;
 
     PriceTrackingDialogCoordinator(Context context, ModalDialogManager modalDialogManager,
-            TabSwitcherMediator.ResetHandler resetHandler, TabModelSelector tabModelSelector) {
+            TabSwitcherMediator.ResetHandler resetHandler, TabModelSelector tabModelSelector,
+            PriceDropNotificationManager notificationManager) {
         mDialogView = (PriceTrackingDialogView) LayoutInflater.from(context).inflate(
                 R.layout.price_tracking_dialog_layout, null, false);
-        mDialogView.setupOnCheckedChangeListener(this);
+        mDialogView.setupTrackPricesSwitchOnCheckedChangeListener(this);
+        mDialogView.setupPriceAlertsArrowOnClickListener(
+                v -> { notificationManager.launchNotificationSettings(); });
         mModalDialogManager = modalDialogManager;
 
         ModalDialogProperties.Controller dialogController = new ModalDialogProperties.Controller() {
@@ -56,12 +60,9 @@
 
     @Override
     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-        if (buttonView.getId() == R.id.track_prices_switch
-                && isChecked != PriceTrackingUtilities.isTrackPricesOnTabsEnabled()) {
+        assert buttonView.getId() == R.id.track_prices_switch;
+        if (isChecked != PriceTrackingUtilities.isTrackPricesOnTabsEnabled()) {
             PriceTrackingUtilities.flipTrackPricesOnTabs();
-        } else if (buttonView.getId() == R.id.price_alerts_switch
-                && isChecked != PriceTrackingUtilities.isPriceDropAlertsEnabled()) {
-            PriceTrackingUtilities.flipPriceDropAlerts();
         }
     }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogView.java
index 79ca84d..9ec729d 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogView.java
@@ -12,13 +12,14 @@
 import androidx.appcompat.widget.SwitchCompat;
 
 import org.chromium.chrome.tab_ui.R;
+import org.chromium.ui.widget.ChromeImageButton;
 
 /**
  * The view for PriceTrackingSettings dialog related UIs.
  */
 public class PriceTrackingDialogView extends LinearLayout {
     private SwitchCompat mTrackPricesSwitch;
-    private SwitchCompat mPriceAlertsSwitch;
+    private ChromeImageButton mPriceAlertsArrow;
 
     public PriceTrackingDialogView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -28,23 +29,29 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mTrackPricesSwitch = (SwitchCompat) findViewById(R.id.track_prices_switch);
-        mPriceAlertsSwitch = (SwitchCompat) findViewById(R.id.price_alerts_switch);
+        mPriceAlertsArrow = (ChromeImageButton) findViewById(R.id.price_alerts_arrow);
     }
 
     /**
-     * Update the checked/unchecked status of two switches. This is called every time before the
-     * dialog shows.
+     * Update the checked/unchecked status of the track prices switch. This is called every time
+     * before the dialog shows.
      */
     void updateSwitch() {
         mTrackPricesSwitch.setChecked(PriceTrackingUtilities.isTrackPricesOnTabsEnabled());
-        mPriceAlertsSwitch.setChecked(PriceTrackingUtilities.isPriceDropAlertsEnabled());
     }
 
     /**
-     * Set the OnCheckedChangeListener of two switches.
+     * Set OnCheckedChangeListener of the track prices switch.
      */
-    void setupOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) {
+    void setupTrackPricesSwitchOnCheckedChangeListener(
+            OnCheckedChangeListener onCheckedChangeListener) {
         mTrackPricesSwitch.setOnCheckedChangeListener(onCheckedChangeListener);
-        mPriceAlertsSwitch.setOnCheckedChangeListener(onCheckedChangeListener);
+    }
+
+    /**
+     * Set OnClickListener of the price alerts arrow button.
+     */
+    void setupPriceAlertsArrowOnClickListener(OnClickListener onClickListener) {
+        mPriceAlertsArrow.setOnClickListener(onClickListener);
     }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingUtilities.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingUtilities.java
index 04b2b60f..dedc625f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingUtilities.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingUtilities.java
@@ -24,8 +24,6 @@
     @VisibleForTesting
     public static final String TRACK_PRICES_ON_TABS =
             ChromePreferenceKeys.PRICE_TRACKING_TRACK_PRICES_ON_TABS;
-    private static final String PRICE_DROP_ALERTS =
-            ChromePreferenceKeys.PRICE_TRACKING_PRICE_DROP_ALERTS;
     @VisibleForTesting
     public static final String PRICE_WELCOME_MESSAGE_CARD =
             ChromePreferenceKeys.PRICE_TRACKING_PRICE_WELCOME_MESSAGE_CARD;
@@ -72,23 +70,6 @@
     }
 
     /**
-     * Update SharedPreferences when users turn on/off the feature getting price drop alerts.
-     */
-    public static void flipPriceDropAlerts() {
-        final boolean enablePriceDropAlerts =
-                SHARED_PREFERENCES_MANAGER.readBoolean(PRICE_DROP_ALERTS, false);
-        SHARED_PREFERENCES_MANAGER.writeBoolean(PRICE_DROP_ALERTS, !enablePriceDropAlerts);
-    }
-
-    /**
-     * @return Whether the feature getting price drop alerts is turned on by users.
-     */
-    public static boolean isPriceDropAlertsEnabled() {
-        return isPriceTrackingEligible()
-                && SHARED_PREFERENCES_MANAGER.readBoolean(PRICE_DROP_ALERTS, false);
-    }
-
-    /**
      * Forbid showing the PriceWelcomeMessageCard any more.
      */
     public static void disablePriceWelcomeMessageCard() {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
index 695ca36..eaff038c 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
@@ -28,6 +28,7 @@
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
 import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
+import org.chromium.chrome.browser.price_tracking.PriceDropNotificationManager;
 import org.chromium.chrome.browser.share.ShareDelegate;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabList;
@@ -324,8 +325,10 @@
             }
 
             if (TabUiFeatureUtilities.isPriceTrackingEnabled()) {
+                PriceDropNotificationManager notificationManager =
+                        new PriceDropNotificationManager();
                 mPriceTrackingDialogCoordinator = new PriceTrackingDialogCoordinator(
-                        context, modalDialogManager, this, mTabModelSelector);
+                        context, modalDialogManager, this, mTabModelSelector, notificationManager);
                 if (!PriceTrackingUtilities.isPriceWelcomeMessageCardDisabled()) {
                     mPriceWelcomeMessageService =
                             new PriceWelcomeMessageService(mTabListCoordinator, mMediator);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMediator.java
index f65163a..3aa50ae 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMediator.java
@@ -680,6 +680,10 @@
                     mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter(),
                     TabUiFeatureUtilities.isTabToGtsAnimationEnabled(), mShowTabsInMruOrder);
             recordTabCounts();
+            // When |mTabModelSelector.isTabStateInitialized| is false and INSTANT_START is enabled,
+            // the scrolling request is already processed in TabModelObserver#restoreCompleted.
+            // Therefore, we only need to handle the case with isTabStateInitialized() here.
+            setInitialScrollIndexOffset();
         } else if (CachedFeatureFlags.isEnabled(ChromeFeatureList.INSTANT_START)) {
             List<PseudoTab> allTabs;
             try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/TasksViewBinderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/TasksViewBinderTest.java
index a057b69..deb02ea2 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/TasksViewBinderTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/TasksViewBinderTest.java
@@ -188,20 +188,20 @@
             mTasksViewPropertyModel.set(IS_FAKE_SEARCH_BOX_VISIBLE, true);
             mTasksViewPropertyModel.set(IS_LENS_BUTTON_VISIBLE, true);
         });
-        assertTrue(isViewVisible(R.id.lens_camera_button));
+        assertTrue(isViewVisible(R.id.lens_camera_button_end));
 
         mViewClicked.set(false);
-        onView(withId(R.id.lens_camera_button)).perform(click());
+        onView(withId(R.id.lens_camera_button_end)).perform(click());
         assertFalse(mViewClicked.get());
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mTasksViewPropertyModel.set(LENS_BUTTON_CLICK_LISTENER, mViewOnClickListener);
         });
-        onView(withId(R.id.lens_camera_button)).perform(click());
+        onView(withId(R.id.lens_camera_button_end)).perform(click());
         assertTrue(mViewClicked.get());
 
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> mTasksViewPropertyModel.set(IS_LENS_BUTTON_VISIBLE, false));
-        assertFalse(isViewVisible(R.id.lens_camera_button));
+        assertFalse(isViewVisible(R.id.lens_camera_button_end));
     }
 
     @Test
@@ -225,7 +225,7 @@
         // Note that onView(R.id.more_tabs).perform(click()) can not be used since it requires 90
         // percent of the view's area is displayed to the users. However, this view has negative
         // margin which makes the percentage is less than 90.
-        // TODO (crbug.com/1025296): Investigate whether this would be a problem for real users.
+        // TODO (crbug.com/1186752): Investigate whether this would be a problem for real users.
         mTasksView.findViewById(R.id.more_tabs).performClick();
         assertFalse(mViewClicked.get());
         mTasksViewPropertyModel.set(MORE_TABS_CLICK_LISTENER, mViewOnClickListener);
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogTest.java
index c0026411..d4cfd5e 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogTest.java
@@ -8,6 +8,8 @@
 import static androidx.test.espresso.Espresso.pressBack;
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.intent.Intents.intended;
+import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
 import static androidx.test.espresso.matcher.RootMatchers.withDecorView;
 import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
@@ -22,12 +24,16 @@
 import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.enterTabSwitcher;
 
 import android.content.res.Configuration;
+import android.os.Build;
+import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.uiautomator.UiDevice;
 import android.view.View;
 import android.widget.TextView;
 
 import androidx.test.espresso.NoMatchingRootException;
+import androidx.test.espresso.intent.Intents;
+import androidx.test.espresso.intent.rule.IntentsTestRule;
 import androidx.test.filters.MediumTest;
 
 import org.junit.After;
@@ -41,6 +47,7 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.tab_ui.R;
@@ -68,6 +75,9 @@
 @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID})
 public class PriceTrackingDialogTest {
     // clang-format on
+    private static final String ACTION_APP_NOTIFICATION_SETTINGS =
+            "android.settings.APP_NOTIFICATION_SETTINGS";
+
     private ModalDialogManager mModalDialogManager;
 
     @Rule
@@ -77,6 +87,10 @@
     public ChromeRenderTestRule mRenderTestRule =
             ChromeRenderTestRule.Builder.withPublicCorpus().build();
 
+    @Rule
+    public IntentsTestRule<ChromeActivity> mIntentTestRule =
+            new IntentsTestRule<>(ChromeActivity.class, false, false);
+
     @Before
     public void setUp() throws Exception {
         PriceTrackingUtilities.setIsSignedInAndSyncEnabledForTesting(true);
@@ -144,21 +158,21 @@
 
     @Test
     @MediumTest
-    public void testPriceAlertsSwitch() {
+    public void testPriceAlertsButton() {
+        Intents.init();
         final ChromeTabbedActivity cta = mActivityTestRule.getActivity();
 
         MenuUtils.invokeCustomMenuActionSync(
                 InstrumentationRegistry.getInstrumentation(), cta, R.id.track_prices_row_menu_id);
         verifyDialogShowing(cta);
+        onView(withId(R.id.price_alerts_arrow)).perform(click());
 
-        onView(withId(R.id.price_alerts_switch)).check(matches(isNotChecked()));
-        assertFalse(PriceTrackingUtilities.isPriceDropAlertsEnabled());
-        onView(withId(R.id.price_alerts_switch)).perform(click());
-        onView(withId(R.id.price_alerts_switch)).check(matches(isChecked()));
-        assertTrue(PriceTrackingUtilities.isPriceDropAlertsEnabled());
-        onView(withId(R.id.price_alerts_switch)).perform(click());
-        onView(withId(R.id.price_alerts_switch)).check(matches(isNotChecked()));
-        assertFalse(PriceTrackingUtilities.isPriceDropAlertsEnabled());
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+            intended(hasAction(ACTION_APP_NOTIFICATION_SETTINGS));
+        } else {
+            intended(hasAction(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS));
+        }
+        Intents.release();
     }
 
     @Test
diff --git a/chrome/android/java/res/drawable/ic_share_small.xml b/chrome/android/java/res/drawable/ic_share_small.xml
deleted file mode 100644
index e8d14fd..0000000
--- a/chrome/android/java/res/drawable/ic_share_small.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2020 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. -->
-
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-    <path
-        android:pathData="M 16.5 15.733 C 15.867 15.733 15.3 15.983 14.867 16.375 L 8.925 12.917 C 8.967 12.725 9 12.533 9 12.333 C 9 12.133 8.967 11.942 8.925 11.75 L 14.8 8.325 C 15.25 8.742 15.842 9 16.5 9 C 17.883 9 19 7.883 19 6.5 C 19 5.117 17.883 4 16.5 4 C 15.117 4 14 5.117 14 6.5 C 14 6.7 14.033 6.892 14.075 7.083 L 8.2 10.508 C 7.75 10.092 7.158 9.833 6.5 9.833 C 5.117 9.833 4 10.95 4 12.333 C 4 13.717 5.117 14.833 6.5 14.833 C 7.158 14.833 7.75 14.575 8.2 14.158 L 14.133 17.625 C 14.092 17.8 14.067 17.983 14.067 18.167 C 14.067 19.508 15.158 20.6 16.5 20.6 C 17.842 20.6 18.933 19.508 18.933 18.167 C 18.933 16.825 17.842 15.733 16.5 15.733 Z"
-        android:fillColor="@color/default_icon_color"
-        android:strokeWidth="1"/>
-</vector>
\ No newline at end of file
diff --git a/chrome/android/java/res/layout/fake_search_box_layout.xml b/chrome/android/java/res/layout/fake_search_box_layout.xml
index 9be6648..6b104d5 100644
--- a/chrome/android/java/res/layout/fake_search_box_layout.xml
+++ b/chrome/android/java/res/layout/fake_search_box_layout.xml
@@ -51,6 +51,18 @@
             style="@style/InputChipWithRemoveIcon" />
     </RelativeLayout>
 
+    <!-- This is a short term solution to have 2 Lens buttons in this xml to
+         support different position variants in experiment.
+         TODO(b/182195615): only keep one Lens button after the experiment. -->
+    <org.chromium.ui.widget.ChromeImageView
+        android:id="@+id/lens_camera_button_start"
+        android:layout_width="48dp"
+        android:layout_height="match_parent"
+        android:contentDescription="@string/accessibility_btn_lens_camera"
+        android:src="@drawable/lens_camera_icon"
+        android:scaleType="centerInside"
+        android:visibility="gone"
+        app:tint="@color/default_icon_color_tint_list" />
     <!-- Padding start is applied so that microphone icon appears in the same location as it
         does in the main url bar. The microphone button in the url bar is 48dp wide with 4dp
         start padding. -->
@@ -64,7 +76,7 @@
         android:scaleType="centerInside"
         app:tint="@color/default_icon_color_tint_list" />
     <org.chromium.ui.widget.ChromeImageView
-        android:id="@+id/lens_camera_button"
+        android:id="@+id/lens_camera_button_end"
         android:layout_width="48dp"
         android:layout_height="match_parent"
         android:contentDescription="@string/accessibility_btn_lens_camera"
diff --git a/chrome/android/java/res/layout/url_action_container.xml b/chrome/android/java/res/layout/url_action_container.xml
index dabc56b..72fdc99a 100644
--- a/chrome/android/java/res/layout/url_action_container.xml
+++ b/chrome/android/java/res/layout/url_action_container.xml
@@ -21,14 +21,24 @@
             android:contentDescription="@string/accessibility_toolbar_btn_delete_url" />
 
         <org.chromium.ui.widget.ChromeImageButton
+            android:id="@+id/lens_camera_button_start"
+            style="@style/LocationBarActionButton"
+            android:src="@drawable/lens_camera_icon"
+            android:visibility="gone"
+            android:contentDescription="@string/accessibility_btn_lens_camera" />
+
+        <org.chromium.ui.widget.ChromeImageButton
             android:id="@+id/mic_button"
             style="@style/LocationBarActionButton"
             android:src="@drawable/btn_mic"
             android:visibility="invisible"
             android:contentDescription="@string/accessibility_toolbar_btn_mic" />
 
+    <!-- This is a short term solution to have 2 Lens buttons in this xml to
+         support different position variants in experiment.
+         TODO(b/182195615): only keep one Lens button after the experiment. -->
         <org.chromium.ui.widget.ChromeImageButton
-            android:id="@+id/lens_camera_button"
+            android:id="@+id/lens_camera_button_end"
             style="@style/LocationBarActionButton"
             android:src="@drawable/lens_camera_icon"
             android:visibility="gone"
diff --git a/chrome/android/java/res/menu/main_menu.xml b/chrome/android/java/res/menu/main_menu.xml
index 69632c9..9917c59 100644
--- a/chrome/android/java/res/menu/main_menu.xml
+++ b/chrome/android/java/res/menu/main_menu.xml
@@ -3,13 +3,6 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<!-- We are working on redesigning menu (crbug.com/1109095), and the icons in
-     here are experimental. If you want to add a new menu item, you need to
-     update both main_menu.xml and main_menu_regroup.xml. If you want to add
-     a new menu item which always shows an icon even not in redesigned menu,
-     please also update AppMenuPropertiesDelegateImpl#prepareCommonMenuItems
-     to keep the icon, like reader_mode_prefs_id.  -->
-
 <!-- TODO(crbug.com/1119550): explore replacing with programatically created
      menu.  -->
 
@@ -45,30 +38,76 @@
         <item android:id="@+id/update_menu_id"
             android:title="@string/menu_update"
             android:icon="@drawable/menu_update" />
-        <item android:id="@+id/move_to_other_window_menu_id"
-            android:title="@string/menu_move_to_other_window"
-            android:icon="@drawable/ic_open_in_browser" />
         <item android:id="@+id/new_tab_menu_id"
             android:title="@string/menu_new_tab"
             android:icon="@drawable/ic_add_box_rounded_corner" />
         <item android:id="@+id/new_incognito_tab_menu_id"
             android:title="@string/menu_new_incognito_tab"
             android:icon="@drawable/incognito_simple" />
-        <item android:id="@+id/all_bookmarks_menu_id"
-            android:title="@string/menu_bookmarks"
-            android:icon="@drawable/btn_star_filled" />
-        <item android:id="@+id/recent_tabs_menu_id"
-            android:title="@string/menu_recent_tabs"
-            android:icon="@drawable/devices_black_24dp" />
+        <item android:id="@+id/move_to_other_window_menu_id"
+            android:title="@string/menu_move_to_other_window"
+            android:icon="@drawable/ic_open_in_browser" />
+        <item android:id="@+id/divider_line_id"
+            android:title="@null" />
         <item android:id="@+id/open_history_menu_id"
             android:title="@string/menu_history"
             android:icon="@drawable/ic_history_googblue_24dp" />
-        <item android:id="@+id/downloads_menu_id"
-            android:title="@string/menu_downloads"
-            android:icon="@drawable/infobar_download_complete" />
-        <item android:id="@+id/translate_id"
-            android:title="@string/menu_translate"
-            android:icon="@drawable/ic_translate" />
+        <item android:id="@+id/downloads_row_menu_id"
+            android:title="@null">
+          <menu>
+                <item android:id="@+id/downloads_menu_id"
+                    android:title="@string/menu_downloads"
+                    android:icon="@drawable/infobar_download_complete" />
+                <item android:id="@+id/offline_page_chip_id"
+                    android:title="@string/menu_download"
+                    android:icon="@drawable/ic_file_download_white_24dp" />
+          </menu>
+        </item>
+        <item android:id="@+id/all_bookmarks_row_menu_id"
+            android:title="@null">
+          <menu>
+                <item android:id="@+id/all_bookmarks_menu_id"
+                    android:title="@string/menu_bookmarks"
+                    android:icon="@drawable/btn_star_filled" />
+                <item android:id="@+id/bookmark_this_page_chip_id"
+                    android:title="@string/menu_bookmark"
+                    android:icon="@drawable/btn_star" />
+          </menu>
+        </item>
+        <item android:id="@+id/recent_tabs_menu_id"
+            android:title="@string/menu_recent_tabs"
+            android:icon="@drawable/devices_black_24dp" />
+        <item android:id="@+id/add_to_divider_line_id"
+            android:title="@null" />
+        <item android:id="@+id/add_to_menu_id"
+            android:title="@string/menu_add_to"
+            android:icon="@drawable/ic_add" >
+          <menu>
+                <item android:id="@+id/add_to_bookmarks_menu_id"
+                    android:title="@string/menu_bookmarks"
+                    android:icon="@drawable/btn_star" />
+                <item android:id="@+id/add_to_reading_list_menu_id"
+                    android:title="@string/reading_list_title"
+                    android:icon="@drawable/ic_reading_list_folder" />
+                <item android:id="@+id/add_to_downloads_menu_id"
+                    android:title="@string/menu_downloads"
+                    android:icon="@drawable/ic_file_download_white_24dp" />
+                <item android:id="@+id/add_to_homescreen_menu_id"
+                    android:title="@string/menu_homescreen"
+                    android:icon="@drawable/ic_add_to_home_screen" />
+          </menu>
+        </item>
+        <!-- Duplicating add_to_homescreen/install_app/open_webapk is for
+             the purpose of experiment, one of them will be removed once the
+             experiments are done. -->
+        <item android:id="@+id/install_app_id"
+            android:title="@string/menu_add_to_homescreen"
+            android:icon="@drawable/ic_add_to_home_screen" />
+        <item android:id="@+id/menu_open_webapk_id"
+            android:title="@string/menu_open_webapk"
+            android:icon="@drawable/ic_add_to_home_screen" />
+        <item android:id="@id/divider_line_id"
+            android:title="@null" />
         <item android:id="@+id/share_row_menu_id"
             android:title="@null">
           <menu>
@@ -82,15 +121,12 @@
         <item android:id="@+id/feed_follow_id"
             android:title="@string/menu_follow"
             android:icon="@drawable/ic_add"/>
-        <item android:id="@+id/get_image_descriptions_id"
-            android:title="@string/menu_get_image_descriptions"
-            android:icon="@drawable/ic_image_descriptions"/>
-        <item android:id="@+id/paint_preview_show_id"
-            android:title="@string/menu_paint_preview_show"
-            android:icon="@drawable/ic_photo_camera" />
         <item android:id="@+id/find_in_page_id"
             android:title="@string/menu_find_in_page"
             android:icon="@drawable/ic_find_in_page" />
+        <item android:id="@+id/translate_id"
+            android:title="@string/menu_translate"
+            android:icon="@drawable/ic_translate" />
         <item android:id="@+id/add_to_homescreen_id"
             android:title="@string/menu_add_to_homescreen"
             android:icon="@drawable/ic_add_to_home_screen" />
@@ -108,6 +144,14 @@
                 android:checkable="true" />
           </menu>
         </item>
+        <item android:id="@+id/paint_preview_show_id"
+            android:title="@string/menu_paint_preview_show"
+            android:icon="@drawable/ic_photo_camera" />
+        <item android:id="@+id/get_image_descriptions_id"
+            android:title="@string/menu_get_image_descriptions"
+            android:icon="@drawable/ic_image_descriptions"/>
+        <item android:id="@id/divider_line_id"
+            android:title="@null" />
         <item android:id="@+id/reader_mode_prefs_id"
             android:title="@string/menu_reader_mode_prefs"
             android:icon="@drawable/reader_mode_prefs_icon" />
@@ -159,18 +203,22 @@
         <item android:id="@id/new_incognito_tab_menu_id"
             android:title="@string/menu_new_incognito_tab"
             android:icon="@drawable/incognito_simple" />
-        <item android:id="@id/all_bookmarks_menu_id"
-            android:title="@string/menu_bookmarks"
-            android:icon="@drawable/btn_star_filled" />
-        <item android:id="@id/recent_tabs_menu_id"
-            android:title="@string/menu_recent_tabs"
-            android:icon="@drawable/devices_black_24dp" />
+        <item android:id="@id/divider_line_id"
+            android:title="@null" />
         <item android:id="@id/open_history_menu_id"
             android:title="@string/menu_history"
             android:icon="@drawable/ic_history_googblue_24dp" />
         <item android:id="@id/downloads_menu_id"
             android:title="@string/menu_downloads"
             android:icon="@drawable/infobar_download_complete" />
+        <item android:id="@id/all_bookmarks_menu_id"
+            android:title="@string/menu_bookmarks"
+            android:icon="@drawable/btn_star_filled" />
+        <item android:id="@id/recent_tabs_menu_id"
+            android:title="@string/menu_recent_tabs"
+            android:icon="@drawable/devices_black_24dp" />
+        <item android:id="@id/divider_line_id"
+            android:title="@null" />
         <item android:id="@id/close_all_tabs_menu_id"
             android:title="@string/menu_close_all_tabs"
             android:icon="@drawable/btn_close_white" />
diff --git a/chrome/android/java/res/menu/main_menu_regroup.xml b/chrome/android/java/res/menu/main_menu_regroup.xml
deleted file mode 100644
index 737b73d..0000000
--- a/chrome/android/java/res/menu/main_menu_regroup.xml
+++ /dev/null
@@ -1,270 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2015 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. -->
-
-<!-- We are working on redesigning menu (crbug.com/1109095), and the icons in
-     here are experimental. If you want to add a new menu item, you need to
-     update both main_menu.xml and main_menu_regroup.xml. If you want to add
-     a new menu item which always shows an icon even not in redesigned menu,
-     please also update AppMenuPropertiesDelegateImpl#prepareCommonMenuItems
-     to keep the icon, like reader_mode_prefs_id.  -->
-
-<!-- TODO(crbug.com/1119550): explore replacing with programatically created
-     menu.  -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- These menu items are common to tablet and phone. -->
-    <group android:id="@+id/PAGE_MENU"
-        android:visible="false">
-        <item android:id="@+id/icon_row_menu_id"
-            android:title="@null">
-            <menu>
-              <item android:id="@+id/backward_menu_id"
-                android:title="@string/accessibility_toolbar_btn_back"
-                android:titleCondensed="@string/back"
-                android:icon="@drawable/btn_back"/>
-              <item android:id="@+id/forward_menu_id"
-                android:title="@string/accessibility_menu_forward"
-                android:titleCondensed="@string/menu_forward"
-                android:icon="@drawable/btn_forward"/>
-              <item android:id="@+id/bookmark_this_page_id"
-                android:title="@string/accessibility_menu_bookmark"
-                android:titleCondensed="@string/menu_bookmark"
-                android:icon="@drawable/btn_star"/>
-              <item android:id="@+id/offline_page_id"
-                android:title="@string/download_page"
-                android:titleCondensed="@string/menu_download"
-                android:icon="@drawable/ic_file_download_white_24dp"/>
-              <item android:id="@+id/share_menu_button_id"
-                android:title="@string/share"
-                android:titleCondensed="@string/share"
-                android:icon="@drawable/ic_share_small" />
-              <item android:id="@+id/info_menu_id"
-                android:title="@string/accessibility_menu_info"
-                android:titleCondensed="@string/menu_page_info"
-                android:icon="@drawable/btn_info" />
-              <item android:id="@+id/reload_menu_id"
-                android:title="@string/accessibility_btn_refresh"
-                android:titleCondensed="@string/menu_refresh"
-                android:icon="@drawable/btn_reload_stop"/>
-            </menu>
-        </item>
-        <item android:id="@+id/update_menu_id"
-            android:title="@string/menu_update"
-            android:icon="@drawable/menu_update" />
-        <item android:id="@+id/new_tab_menu_id"
-            android:title="@string/menu_new_tab"
-            android:icon="@drawable/ic_add_box_rounded_corner" />
-        <item android:id="@+id/new_incognito_tab_menu_id"
-            android:title="@string/menu_new_incognito_tab"
-            android:icon="@drawable/incognito_simple" />
-        <item android:id="@+id/move_to_other_window_menu_id"
-            android:title="@string/menu_move_to_other_window"
-            android:icon="@drawable/ic_open_in_browser" />
-        <item android:id="@+id/divider_line_id"
-            android:title="@null" />
-        <item android:id="@+id/open_history_menu_id"
-            android:title="@string/menu_history"
-            android:icon="@drawable/ic_history_googblue_24dp" />
-        <item android:id="@+id/downloads_row_menu_id"
-            android:title="@null">
-          <menu>
-                <item android:id="@+id/downloads_menu_id"
-                    android:title="@string/menu_downloads"
-                    android:icon="@drawable/infobar_download_complete" />
-                <item android:id="@+id/offline_page_chip_id"
-                    android:title="@string/menu_download"
-                    android:icon="@drawable/ic_file_download_white_24dp" />
-          </menu>
-        </item>
-        <item android:id="@+id/all_bookmarks_row_menu_id"
-            android:title="@null">
-          <menu>
-                <item android:id="@+id/all_bookmarks_menu_id"
-                    android:title="@string/menu_bookmarks"
-                    android:icon="@drawable/btn_star_filled" />
-                <item android:id="@+id/bookmark_this_page_chip_id"
-                    android:title="@string/menu_bookmark"
-                    android:icon="@drawable/btn_star" />
-          </menu>
-        </item>
-        <item android:id="@+id/recent_tabs_menu_id"
-            android:title="@string/menu_recent_tabs"
-            android:icon="@drawable/devices_black_24dp" />
-        <item android:id="@+id/add_to_divider_line_id"
-            android:title="@null" />
-        <item android:id="@+id/add_to_menu_id"
-            android:title="@string/menu_add_to"
-            android:icon="@drawable/ic_add" >
-          <menu>
-                <item android:id="@+id/add_to_bookmarks_menu_id"
-                    android:title="@string/menu_bookmarks"
-                    android:icon="@drawable/btn_star" />
-                <item android:id="@+id/add_to_reading_list_menu_id"
-                    android:title="@string/reading_list_title"
-                    android:icon="@drawable/ic_reading_list_folder" />
-                <item android:id="@+id/add_to_downloads_menu_id"
-                    android:title="@string/menu_downloads"
-                    android:icon="@drawable/ic_file_download_white_24dp" />
-                <item android:id="@+id/add_to_homescreen_menu_id"
-                    android:title="@string/menu_homescreen"
-                    android:icon="@drawable/ic_add_to_home_screen" />
-          </menu>
-        </item>
-        <!-- Duplicating add_to_homescreen/install_app/open_webapk is for
-             the purpose of experiment, one of them will be removed once the
-             experiments are done. -->
-        <item android:id="@+id/install_app_id"
-            android:title="@string/menu_add_to_homescreen"
-            android:icon="@drawable/ic_add_to_home_screen" />
-        <item android:id="@+id/menu_open_webapk_id"
-            android:title="@string/menu_open_webapk"
-            android:icon="@drawable/ic_add_to_home_screen" />
-        <item android:id="@id/divider_line_id"
-            android:title="@null" />
-        <item android:id="@+id/share_row_menu_id"
-            android:title="@null">
-          <menu>
-              <item android:id="@+id/share_menu_id"
-                android:title="@string/menu_share_page"
-                android:icon="@drawable/ic_share_white_24dp" />
-              <item android:id="@+id/direct_share_menu_id"
-                android:title="@null" />
-          </menu>
-        </item>
-        <item android:id="@+id/feed_follow_id"
-            android:title="@string/menu_follow"
-            android:icon="@drawable/ic_add"/>
-        <item android:id="@+id/find_in_page_id"
-            android:title="@string/menu_find_in_page"
-            android:icon="@drawable/ic_find_in_page" />
-        <item android:id="@+id/translate_id"
-            android:title="@string/menu_translate"
-            android:icon="@drawable/ic_translate" />
-        <item android:id="@+id/add_to_homescreen_id"
-            android:title="@string/menu_add_to_homescreen"
-            android:icon="@drawable/ic_add_to_home_screen" />
-        <item android:id="@+id/open_webapk_id"
-            android:title="@string/menu_open_webapk"
-            android:icon="@drawable/ic_add_to_home_screen" />
-        <item android:id="@+id/request_desktop_site_row_menu_id"
-            android:title="@null">
-          <menu>
-              <item android:id="@+id/request_desktop_site_id"
-                android:title="@string/menu_request_desktop_site"
-                android:icon="@drawable/ic_desktop_windows" />
-              <item android:id="@+id/request_desktop_site_check_id"
-                android:title="@null"
-                android:checkable="true" />
-          </menu>
-        </item>
-        <item android:id="@+id/paint_preview_show_id"
-            android:title="@string/menu_paint_preview_show"
-            android:icon="@drawable/ic_photo_camera" />
-        <item android:id="@+id/get_image_descriptions_id"
-            android:title="@string/menu_get_image_descriptions"
-            android:icon="@drawable/ic_image_descriptions"/>
-        <item android:id="@id/divider_line_id"
-            android:title="@null" />
-        <item android:id="@+id/reader_mode_prefs_id"
-            android:title="@string/menu_reader_mode_prefs"
-            android:icon="@drawable/reader_mode_prefs_icon" />
-        <item android:id="@+id/preferences_id"
-            android:title="@string/menu_settings"
-            android:icon="@drawable/settings_cog" />
-        <item android:id="@+id/info_id"
-            android:title="@string/menu_page_info"
-            android:icon="@drawable/btn_info" />
-        <item android:id="@+id/help_id"
-            android:title="@string/menu_help"
-            android:icon="@drawable/help_outline" />
-        <item android:id="@+id/enter_vr_id"
-            android:title="@string/enter_vr"
-            android:icon="@drawable/vr_headset" />
-        <item android:id="@+id/managed_by_menu_id"
-            android:title="@string/managed" />
-    </group>
-
-    <!-- Items shown only in the tab switcher -->
-    <group android:id="@+id/OVERVIEW_MODE_MENU"
-        android:visible="false">
-        <item android:id="@id/new_tab_menu_id"
-            android:title="@string/menu_new_tab"
-            android:icon="@drawable/ic_add_box_rounded_corner" />
-        <item android:id="@id/new_incognito_tab_menu_id"
-            android:title="@string/menu_new_incognito_tab"
-            android:icon="@drawable/incognito_simple" />
-        <item android:id="@+id/close_all_tabs_menu_id"
-            android:title="@string/menu_close_all_tabs"
-            android:icon="@drawable/btn_close_white" />
-        <item android:id="@+id/close_all_incognito_tabs_menu_id"
-            android:title="@string/menu_close_all_incognito_tabs"
-            android:icon="@drawable/btn_close_white" />
-        <item android:id="@+id/menu_group_tabs"
-            android:title="@string/menu_group_tabs"
-            android:icon="@drawable/ic_widgets" />
-        <item android:id="@+id/track_prices_row_menu_id"
-            android:title="@string/menu_track_prices"
-            android:icon="@drawable/ic_trending_down_black" />
-        <item android:id="@id/preferences_id"
-            android:title="@string/menu_settings"
-            android:icon="@drawable/settings_cog" />
-    </group>
-
-    <!-- Items shown only in the tab switcher when start surface is enabled -->
-    <group android:id="@+id/START_SURFACE_MODE_MENU"
-        android:visible="false">
-        <item android:id="@id/new_tab_menu_id"
-            android:title="@string/menu_new_tab"
-            android:icon="@drawable/ic_add_box_rounded_corner" />
-        <item android:id="@id/new_incognito_tab_menu_id"
-            android:title="@string/menu_new_incognito_tab"
-            android:icon="@drawable/incognito_simple" />
-        <item android:id="@id/divider_line_id"
-            android:title="@null" />
-        <item android:id="@id/open_history_menu_id"
-            android:title="@string/menu_history"
-            android:icon="@drawable/ic_history_googblue_24dp" />
-        <item android:id="@id/downloads_menu_id"
-            android:title="@string/menu_downloads"
-            android:icon="@drawable/infobar_download_complete" />
-        <item android:id="@id/all_bookmarks_menu_id"
-            android:title="@string/menu_bookmarks"
-            android:icon="@drawable/btn_star_filled" />
-        <item android:id="@id/recent_tabs_menu_id"
-            android:title="@string/menu_recent_tabs"
-            android:icon="@drawable/devices_black_24dp" />
-        <item android:id="@id/divider_line_id"
-            android:title="@null" />
-        <item android:id="@id/close_all_tabs_menu_id"
-            android:title="@string/menu_close_all_tabs"
-            android:icon="@drawable/btn_close_white" />
-        <item android:id="@id/close_all_incognito_tabs_menu_id"
-            android:title="@string/menu_close_all_incognito_tabs"
-            android:icon="@drawable/btn_close_white" />
-        <item android:id="@id/menu_group_tabs"
-            android:title="@string/menu_group_tabs"
-            android:icon="@drawable/ic_widgets" />
-        <item android:id="@id/track_prices_row_menu_id"
-            android:title="@string/menu_track_prices"
-            android:icon="@drawable/ic_trending_down_black" />
-        <item android:id="@id/preferences_id"
-            android:title="@string/menu_settings"
-            android:icon="@drawable/settings_cog" />
-    </group>
-
-    <!-- Items shown only when the tablet has no visible tabs -->
-    <group android:id="@+id/TABLET_EMPTY_MODE_MENU"
-        android:visible="false">
-        <item android:id="@id/new_tab_menu_id"
-            android:title="@string/menu_new_tab"
-            android:icon="@drawable/ic_add_box_rounded_corner" />
-        <item android:id="@id/new_incognito_tab_menu_id"
-            android:title="@string/menu_new_incognito_tab"
-            android:icon="@drawable/incognito_simple" />
-        <item android:id="@id/preferences_id"
-            android:title="@string/menu_settings"
-            android:icon="@drawable/settings_cog" />
-    </group>
-</menu>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
index fcb464e..9cf912f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
@@ -211,9 +211,6 @@
   "AndroidPaymentApp\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
-  "AndroidPaymentAppFinder\.java": [
-    "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
-  ],
   "AutofillPaymentInstrument\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index 86b21b3e..2aabc62 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -2217,15 +2217,6 @@
             return false;
         }
 
-        if (id == R.id.backward_menu_id) {
-            if (currentTab.canGoBack()) {
-                currentTab.goBack();
-                RecordUserAction.record("MobileMenuBackward");
-                return true;
-            }
-            return false;
-        }
-
         if (id == R.id.forward_menu_id) {
             if (currentTab.canGoForward()) {
                 currentTab.goForward();
@@ -2269,7 +2260,7 @@
             return true;
         }
 
-        if (id == R.id.info_menu_id || id == R.id.info_id) {
+        if (id == R.id.info_menu_id) {
             WebContents webContents = currentTab.getWebContents();
             PageInfoController.show(this, webContents, null,
                     PageInfoController.OpenedFromSource.MENU,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
index 3ddad50d..2ee7c152 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
@@ -85,9 +85,6 @@
  * items based on activity state.
  */
 public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate {
-    public static final StringCachedFieldTrialParameter ACTION_BAR_VARIATION =
-            new StringCachedFieldTrialParameter(
-                    ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP, "action_bar", "");
     public static final StringCachedFieldTrialParameter THREE_BUTTON_ACTION_BAR_VARIATION =
             new StringCachedFieldTrialParameter(
                     ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR,
@@ -132,13 +129,6 @@
         int TABLET_EMPTY_MODE_MENU = 3;
     }
 
-    @IntDef({ActionBarType.STANDARD, ActionBarType.BACKWARD_BUTTON, ActionBarType.SHARE_BUTTON})
-    @interface ActionBarType {
-        int STANDARD = 0;
-        int BACKWARD_BUTTON = 1;
-        int SHARE_BUTTON = 2;
-    }
-
     @IntDef({ThreeButtonActionBarType.DISABLED, ThreeButtonActionBarType.ACTION_CHIP_VIEW,
             ThreeButtonActionBarType.DESTINATION_CHIP_VIEW, ThreeButtonActionBarType.ADD_TO_OPTION})
     @interface ThreeButtonActionBarType {
@@ -225,9 +215,6 @@
 
     @Override
     public int getAppMenuLayoutId() {
-        if (shouldShowRegroupedMenu() || shouldShowThreeButtonActionBar()) {
-            return R.menu.main_menu_regroup;
-        }
         return R.menu.main_menu;
     }
 
@@ -321,17 +308,6 @@
         if (shouldShowIconRow) {
             SubMenu actionBar = menu.findItem(R.id.icon_row_menu_id).getSubMenu();
 
-            @ActionBarType
-            int actionBarType = getActionBarType();
-            MenuItem backwardMenuItem = actionBar.findItem(R.id.backward_menu_id);
-            if (backwardMenuItem != null) {
-                if (actionBarType == ActionBarType.BACKWARD_BUTTON) {
-                    backwardMenuItem.setEnabled(currentTab.canGoBack());
-                } else {
-                    actionBar.removeItem(R.id.backward_menu_id);
-                }
-            }
-
             // Disable the "Forward" menu item if there is no page to go to.
             MenuItem forwardMenuItem = actionBar.findItem(R.id.forward_menu_id);
             forwardMenuItem.setEnabled(currentTab.canGoForward());
@@ -360,19 +336,6 @@
                 }
             }
 
-            MenuItem shareMenuItem = actionBar.findItem(R.id.share_menu_button_id);
-            if (shareMenuItem != null) {
-                if (shouldShowShareInMenu()) {
-                    actionBar.removeItem(R.id.share_menu_button_id);
-                } else {
-                    shareMenuItem.setEnabled(mShareUtils.shouldEnableShare(currentTab));
-                }
-            }
-
-            if (shouldShowInfoInMenu()) {
-                actionBar.removeItem(R.id.info_menu_id);
-            }
-
             if (shouldShowThreeButtonActionBar()) {
                 assert actionBar.size() == 3;
             } else {
@@ -474,8 +437,7 @@
         }
 
         // Don't allow either "chrome://" pages or interstitial pages to be shared.
-        menu.findItem(R.id.share_row_menu_id)
-                .setVisible(mShareUtils.shouldEnableShare(currentTab) && shouldShowShareInMenu());
+        menu.findItem(R.id.share_row_menu_id).setVisible(mShareUtils.shouldEnableShare(currentTab));
 
         ShareHelper.configureDirectShareMenuItem(
                 mContext, menu.findItem(R.id.direct_share_menu_id));
@@ -542,11 +504,6 @@
         // Only display reader mode settings menu option if the current page is in reader mode.
         menu.findItem(R.id.reader_mode_prefs_id).setVisible(shouldShowReaderModePrefs(currentTab));
 
-        MenuItem infoMenuItem = menu.findItem(R.id.info_id);
-        if (infoMenuItem != null) {
-            infoMenuItem.setVisible(shouldShowInfoInMenu());
-        }
-
         // Only display the Enter VR button if VR Shell Dev environment is enabled.
         menu.findItem(R.id.enter_vr_id).setVisible(shouldShowEnterVr());
 
@@ -990,37 +947,11 @@
         sItemBookmarkedForTesting = bookmarked;
     }
 
-    private boolean shouldShowRegroupedMenu() {
-        return CachedFeatureFlags.isEnabled(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP);
-    }
-
     private static boolean shouldShowThreeButtonActionBar() {
         return CachedFeatureFlags.isEnabled(
                 ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR);
     }
 
-    private boolean shouldShowShareInMenu() {
-        return getActionBarType() != ActionBarType.SHARE_BUTTON;
-    }
-
-    private boolean shouldShowInfoInMenu() {
-        return getActionBarType() != ActionBarType.STANDARD;
-    }
-
-    /**
-     * @return The type of action bar should be shown.
-     */
-    private @ActionBarType int getActionBarType() {
-        if (shouldShowRegroupedMenu()) {
-            if (ACTION_BAR_VARIATION.getValue().equals("backward_button")) {
-                return ActionBarType.BACKWARD_BUTTON;
-            } else if (ACTION_BAR_VARIATION.getValue().equals("share_button")) {
-                return ActionBarType.SHARE_BUTTON;
-            }
-        }
-        return ActionBarType.STANDARD;
-    }
-
     /**
      * @return The type of three button action bar should be shown.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
index b2b0deb..1d37106 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
@@ -81,8 +81,6 @@
                 ChromeFeatureList.TAB_GROUPS_ANDROID,
                 ChromeFeatureList.TAB_GROUPS_CONTINUATION_ANDROID,
                 ChromeFeatureList.TAB_TO_GTS_ANIMATION,
-                ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_ICONS,
-                ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP,
                 ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR,
                 ChromeFeatureList.THEME_REFACTOR_ANDROID,
                 ChromeFeatureList.TOOLBAR_USE_HARDWARE_BITMAP_DRAW,
@@ -96,13 +94,13 @@
         // clang-format off
         List<CachedFieldTrialParameter> fieldTrialsToCache = Arrays.asList(
                 AdaptiveToolbarFeatures.MODE_PARAM,
-                AppMenuPropertiesDelegateImpl.ACTION_BAR_VARIATION,
                 AppMenuPropertiesDelegateImpl.THREE_BUTTON_ACTION_BAR_VARIATION,
                 ConditionalTabStripUtils.CONDITIONAL_TAB_STRIP_INFOBAR_LIMIT,
                 ConditionalTabStripUtils.CONDITIONAL_TAB_STRIP_INFOBAR_PERIOD,
                 ConditionalTabStripUtils.CONDITIONAL_TAB_STRIP_SESSION_TIME_MS,
                 LensFeature.DISABLE_LENS_CAMERA_ASSISTED_SEARCH_ON_INCOGNITO,
                 LensFeature.ENABLE_LENS_CAMERA_ASSISTED_SEARCH_ON_LOW_END_DEVICE,
+                LensFeature.SEARCH_BOX_START_VARIANT_LENS_CAMERA_ASSISTED_SEARCH,
                 LensFeature.MIN_AGSA_VERSION_LENS_CAMERA_ASSISTED_SEARCH,
                 PageAnnotationsServiceConfig.PAGE_ANNOTATIONS_BASE_URL,
                 ReturnToChromeExperimentsUtil.TAB_SWITCHER_ON_RETURN_MS,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/lens/LensFeature.java b/chrome/android/java/src/org/chromium/chrome/browser/lens/LensFeature.java
index 55c4b8a1..74c026d4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/lens/LensFeature.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/lens/LensFeature.java
@@ -8,6 +8,9 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.StringCachedFieldTrialParameter;
 
+/**
+ * A class contains cached Lens feature flags and params.
+ */
 public class LensFeature {
     private static final String DISABLE_ON_INCOGNITO_PARAM_NAME = "disableOnIncognito";
     public static final BooleanCachedFieldTrialParameter
@@ -29,4 +32,12 @@
             MIN_AGSA_VERSION_LENS_CAMERA_ASSISTED_SEARCH = new StringCachedFieldTrialParameter(
                     ChromeFeatureList.LENS_CAMERA_ASSISTED_SEARCH,
                     MIN_AGSA_VERSION_LENS_CAMERA_ASSISTED_SEARCH_PARAM_NAME, "12.7");
+
+    private static final String SEARCH_BOX_START_VARIANT_LENS_CAMERA_ASSISTED_SEARCH_PARAM_NAME =
+            "searchBoxStartVariantForLensCameraAssistedSearch";
+    public static final BooleanCachedFieldTrialParameter
+            SEARCH_BOX_START_VARIANT_LENS_CAMERA_ASSISTED_SEARCH =
+                    new BooleanCachedFieldTrialParameter(
+                            ChromeFeatureList.LENS_CAMERA_ASSISTED_SEARCH,
+                            SEARCH_BOX_START_VARIANT_LENS_CAMERA_ASSISTED_SEARCH_PARAM_NAME, false);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
index f8d9ea7f..24cf07b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
@@ -37,6 +37,7 @@
 import org.chromium.chrome.browser.explore_sites.ExploreSitesBridge;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.lens.LensEntryPoint;
+import org.chromium.chrome.browser.lens.LensFeature;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.ntp.LogoBridge.Logo;
@@ -366,7 +367,10 @@
             // remaining 16dp evenly between start/end resulting in a paddingEnd of 8dp.
             int paddingStart = getResources().getDimensionPixelSize(
                     R.dimen.sei_ntp_fakebox_button_start_padding);
-            ImageView lensButton = findViewById(R.id.lens_camera_button);
+            ImageView lensButton =
+                    LensFeature.SEARCH_BOX_START_VARIANT_LENS_CAMERA_ASSISTED_SEARCH.getValue()
+                    ? findViewById(R.id.lens_camera_button_start)
+                    : findViewById(R.id.lens_camera_button_end);
             lensButton.setPaddingRelative(paddingStart, lensButton.getPaddingTop(), getPaddingEnd(),
                     lensButton.getPaddingBottom());
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxViewBinder.java
index ba352c2..273451f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxViewBinder.java
@@ -10,6 +10,7 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.lens.LensFeature;
 import org.chromium.ui.base.ViewUtils;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -25,7 +26,10 @@
     public final void bind(PropertyModel model, View view, PropertyKey propertyKey) {
         ImageView voiceSearchButton =
                 view.findViewById(org.chromium.chrome.R.id.voice_search_button);
-        ImageView lensButton = view.findViewById(org.chromium.chrome.R.id.lens_camera_button);
+        ImageView lensButton =
+                LensFeature.SEARCH_BOX_START_VARIANT_LENS_CAMERA_ASSISTED_SEARCH.getValue()
+                ? view.findViewById(org.chromium.chrome.R.id.lens_camera_button_start)
+                : view.findViewById(org.chromium.chrome.R.id.lens_camera_button_end);
         View searchBoxContainer = view;
         final TextView searchBoxTextView = searchBoxContainer.findViewById(R.id.search_box_text);
         final ChipView chipView = searchBoxContainer.findViewById(R.id.query_tiles_chip);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/DEPS
index a1a0b06..9c758c72 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/DEPS
@@ -16,6 +16,7 @@
   "+chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/lens/LensController.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/lens/LensEntryPoint.java",
+  "+chrome/android/java/src/org/chromium/chrome/browser/lens/LensFeature.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/lens/LensIntentParams.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/lens/LensQueryParams.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
index b88f01c..7912599b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
@@ -20,6 +20,7 @@
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.lens.LensController;
+import org.chromium.chrome.browser.lens.LensFeature;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
 import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
@@ -163,7 +164,9 @@
         mMicButton = mLocationBarLayout.findViewById(R.id.mic_button);
         mMicButton.setOnClickListener(mLocationBarMediator::micButtonClicked);
 
-        mLensButton = mLocationBarLayout.findViewById(R.id.lens_camera_button);
+        mLensButton = LensFeature.SEARCH_BOX_START_VARIANT_LENS_CAMERA_ASSISTED_SEARCH.getValue()
+                ? mLocationBarLayout.findViewById(R.id.lens_camera_button_start)
+                : mLocationBarLayout.findViewById(R.id.lens_camera_button_end);
         mLensButton.setOnClickListener(mLocationBarMediator::lensButtonClicked);
 
         mUrlBar.setOnKeyListener(mLocationBarMediator);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
index 29532d7..f87280e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -24,6 +24,7 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.lens.LensFeature;
 import org.chromium.chrome.browser.omnibox.status.StatusCoordinator;
 import org.chromium.chrome.browser.omnibox.status.StatusView;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator;
@@ -71,7 +72,9 @@
         mDeleteButton = findViewById(R.id.delete_button);
         mUrlBar = findViewById(R.id.url_bar);
         mMicButton = findViewById(R.id.mic_button);
-        mLensButton = findViewById(R.id.lens_camera_button);
+        mLensButton = LensFeature.SEARCH_BOX_START_VARIANT_LENS_CAMERA_ASSISTED_SEARCH.getValue()
+                ? findViewById(R.id.lens_camera_button_start)
+                : findViewById(R.id.lens_camera_button_end);
         mUrlActionContainer = (LinearLayout) findViewById(R.id.url_action_container);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
index 9a58999..ce5219a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
@@ -1130,8 +1130,6 @@
     @Override
     public void onIncognitoStateChanged() {
         updateMicButtonState();
-        // Status icon might be animated out in incognito mode. Make sure it's restored.
-        mLocationBarLayout.updateStatusVisibility();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
index a1906f9..366e74a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
@@ -132,9 +132,6 @@
                 mLocationBarDataProvider.isIncognito());
         setShowIconsWhenUrlFocused(shouldShowLogo);
 
-        // Reset the alpha of the StatusIcon.
-        mStatusView.setAlpha(1.0f);
-
         if (!shouldShowLogo) return;
 
         if (mLocationBarDataProvider.isInOverviewAndShowingOmnibox()) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusCoordinator.java
index f0f5b53..d6d482c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusCoordinator.java
@@ -78,11 +78,10 @@
 
         Runnable forceModelViewReconciliationRunnable = () -> {
             final View securityIconView = getSecurityIconView();
-            mStatusView.setStatusIconAlpha(mModel.get(StatusProperties.STATUS_ICON_ALPHA));
-            securityIconView.setVisibility(mModel.get(StatusProperties.STATUS_ICON_ALPHA) > 0
-                                    && mModel.get(StatusProperties.SHOW_STATUS_ICON)
-                            ? View.VISIBLE
-                            : View.GONE);
+            mStatusView.setAlpha(1f);
+            securityIconView.setAlpha(mModel.get(StatusProperties.STATUS_ICON_ALPHA));
+            securityIconView.setVisibility(
+                    mModel.get(StatusProperties.SHOW_STATUS_ICON) ? View.VISIBLE : View.GONE);
         };
 
         PageInfoIPHController pageInfoIPHController = new PageInfoIPHController(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
index 32fb617..6ba7dca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
@@ -83,7 +83,6 @@
 
     private final PermissionDialogController mPermissionDialogController;
     private final Handler mPermissionTaskHandler = new Handler();
-    private final Runnable mForceModelViewReconciliationRunnable;
     @ContentSettingsType
     private int mLastPermission = ContentSettingsType.DEFAULT;
     private final PageInfoIPHController mPageInfoIPHController;
@@ -93,6 +92,8 @@
     private float mUrlFocusPercent;
     private String mSearchEngineLogoUrl;
 
+    private Runnable mForceModelViewReconciliationRunnable;
+
     // Factors used to offset the animation of the status icon's alpha adjustment. The full formula
     // used: alpha = (focusAnimationProgress - mTextOffsetThreshold) / (1 - mTextOffsetThreshold)
     // mTextOffsetThreshold will be the % space that the icon takes up during the focus animation.
@@ -637,22 +638,25 @@
 
     /**
      * Temporary workaround for the divergent logic for status icon visibility changes for the dse
-     * icon experiment.
+     * icon experiment. Should be removed when the dse icon launches (crbug.com/1019488).
      *
-     * The logic below resets the visual state for the StatusView widget when transitioning back
-     * and forth between incognito and non-incognito states.
-     * When the UrlBar is the first visible view when focused, the StatusView's alpha
+     * When transitioning to incognito, the first visible view when focused will be assigned to
+     * UrlBar. When the UrlBar is the first visible view when focused, the StatusView's alpha
      * will be set to 0 in LocationBarPhone#populateFadeAnimations. When transitioning back from
      * incognito, StatusView's state needs to be reset to match the current state of the status view
      * {@link org.chromium.chrome.browser.omnibox.LocationBarPhone#updateVisualsForState}.
      * property model.
-     *
-     * TODO(http://crbug.com/1185985): Delete this when all the code altering StatusView properties
-     * is consolidated and it is safe to do so.
      **/
     private void reconcileVisualState() {
         // No reconciliation is needed on tablet because the status icon is always shown.
         if (mIsTablet) return;
+
+        if (!mShowStatusIconWhenUrlFocused || mLocationBarDataProvider.isIncognito()
+                || !mSearchEngineLogoUtils.shouldShowSearchEngineLogo(
+                        mLocationBarDataProvider.isIncognito())) {
+            return;
+        }
+
         assert mForceModelViewReconciliationRunnable != null;
         mForceModelViewReconciliationRunnable.run();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusView.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusView.java
index ab6a8ad..d5af19e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusView.java
@@ -4,9 +4,6 @@
 
 package org.chromium.chrome.browser.omnibox.status;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -63,9 +60,6 @@
     private View mSeparatorView;
     private View mStatusExtraSpace;
 
-    // Drawable alpha ranges from 0 (transparent) to 255 (opaque).
-    private final ValueAnimator mDrawableAlphaAnimator = ValueAnimator.ofInt(0, 255);
-
     private boolean mAnimationsEnabled;
     private boolean mAnimatingStatusIconShow;
     private boolean mAnimatingStatusIconHide;
@@ -95,44 +89,6 @@
         mVerboseStatusTextView = findViewById(R.id.location_bar_verbose_status);
         mSeparatorView = findViewById(R.id.location_bar_verbose_status_separator);
         mStatusExtraSpace = findViewById(R.id.location_bar_verbose_status_extra_space);
-        mDrawableAlphaAnimator.addUpdateListener(animator -> {
-            int opacity = (int) animator.getAnimatedValue();
-            // Note: the mStatusIconDrawable does not become 'current' until the transition
-            // animation finishes, eg. it will be <null> when the fade out begins, but will only
-            // substitute the currently shown drawable at the very last moment.
-            Drawable drawable = mIconView.getDrawable();
-            if (drawable != null) {
-                drawable.setAlpha(opacity);
-            }
-        });
-        mDrawableAlphaAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animator) {
-                if (mAnimatingStatusIconShow) {
-                    mIconView.setVisibility(View.VISIBLE);
-                    updateIncognitoBadgeEndPadding();
-                }
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animator) {
-                mAnimatingStatusIconHide = false;
-                mAnimatingStatusIconShow = false;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                if (mAnimatingStatusIconHide) {
-                    mIconView.setVisibility(View.GONE);
-                    // Update incognito badge padding after the animation to avoid a glitch
-                    // on focusing location bar.
-                    updateIncognitoBadgeEndPadding();
-                }
-                updateTouchDelegate();
-                mAnimatingStatusIconHide = false;
-                mAnimatingStatusIconShow = false;
-            }
-        });
 
         configureAccessibilityDescriptions();
     }
@@ -203,21 +159,46 @@
             return;
         }
 
-        mDrawableAlphaAnimator.cancel();
         if (!wantIconHidden && (isIconHidden || mAnimatingStatusIconHide)) {
             // Action 1: animate showing, if icon was either hidden or hiding.
+            if (mAnimatingStatusIconHide) mIconView.animate().cancel();
+            mAnimatingStatusIconHide = false;
+
             mAnimatingStatusIconShow = true;
-            mDrawableAlphaAnimator.setDuration(ICON_ANIMATION_DURATION_MS).start();
+            mIconView.setVisibility(View.VISIBLE);
+            updateIncognitoBadgeEndPadding();
+            mIconView.animate()
+                    .alpha(1.0f)
+                    .setDuration(ICON_ANIMATION_DURATION_MS)
+                    .withEndAction(() -> {
+                        mAnimatingStatusIconShow = false;
+                        // Wait until the icon is visible so the bounds will be properly set.
+                        updateTouchDelegate();
+                    })
+                    .start();
         } else if (wantIconHidden && (!isIconHidden || mAnimatingStatusIconShow)) {
             // Action 2: animate hiding, if icon was either shown or showing.
+            if (mAnimatingStatusIconShow) mIconView.animate().cancel();
+            mAnimatingStatusIconShow = false;
+
+            mAnimatingStatusIconHide = true;
             // Do not animate phase-out when animations are disabled.
             // While this looks nice in some cases (navigating to insecure sites),
             // it has a side-effect of briefly showing padlock (phase-out) when navigating
             // back and forth between secure and insecure sites, which seems like a glitch.
             // See bug: crbug.com/919449
-            mAnimatingStatusIconHide = true;
-            mDrawableAlphaAnimator.setDuration(mAnimationsEnabled ? ICON_ANIMATION_DURATION_MS : 0)
-                    .reverse();
+            mIconView.animate()
+                    .setDuration(mAnimationsEnabled ? ICON_ANIMATION_DURATION_MS : 0)
+                    .alpha(0.0f)
+                    .withEndAction(() -> {
+                        mIconView.setVisibility(View.GONE);
+                        mAnimatingStatusIconHide = false;
+                        // Update incognito badge padding after the animation to avoid a glitch on
+                        // focusing location bar.
+                        updateIncognitoBadgeEndPadding();
+                        updateTouchDelegate();
+                    })
+                    .start();
         } else {
             updateTouchDelegate();
         }
@@ -344,17 +325,14 @@
 
     void setStatusIconResources(
             @Nullable Drawable statusIconDrawable, @IconTransitionType int transitionType) {
-        mStatusIconDrawable = statusIconDrawable == null ? null : statusIconDrawable.mutate();
+        mStatusIconDrawable = statusIconDrawable;
         animateStatusIcon(transitionType);
     }
 
     /** Specify the status icon alpha. */
     void setStatusIconAlpha(float alpha) {
         if (mIconView == null) return;
-        // Cancel animation if it is currently happening and yield control.
-        mDrawableAlphaAnimator.cancel();
-        mDrawableAlphaAnimator.setDuration(ICON_ANIMATION_DURATION_MS)
-                .setCurrentPlayTime(Math.round(ICON_ANIMATION_DURATION_MS * alpha));
+        mIconView.setAlpha(alpha);
     }
 
     /** Specify the status icon visibility. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
index f105b02..6fa0b33 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
@@ -47,6 +47,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.chrome.browser.tabmodel.TabWindowManager;
+import org.chromium.components.metrics.OmniboxEventProtos.OmniboxEventProto.PageClassification;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.AutocompleteResult;
 import org.chromium.components.query_tiles.QueryTile;
@@ -337,7 +338,21 @@
             mUrlFocusTime = System.currentTimeMillis();
             setSuggestionVisibilityState(SuggestionVisibilityState.PENDING_ALLOW);
 
-            postAutocompleteRequest(this::startZeroSuggest, SCHEDULE_FOR_IMMEDIATE_EXECUTION);
+            // Ask directly for zero-suggestions related to current input, unless the user is
+            // currently visiting SearchActivity and the input is populated from the launch intent.
+            // For SearchActivity, in most cases the input will be empty, triggering the same
+            // response (starting zero suggestions), but if the Activity was launched with a QUERY,
+            // then the query might point to a different URL than the reported Page, and the
+            // suggestion would take the user to the DSE home page.
+            // This is tracked by MobileStartup.LaunchCause / EXTERNAL_SEARCH_ACTION_INTENT
+            // metric.
+            if (mDataProvider.getPageClassification(false)
+                    != PageClassification.ANDROID_SEARCH_WIDGET_VALUE) {
+                postAutocompleteRequest(this::startZeroSuggest, SCHEDULE_FOR_IMMEDIATE_EXECUTION);
+            } else {
+                String text = mUrlBarEditingTextProvider.getTextWithoutAutocomplete();
+                onTextChanged(text, text);
+            }
         } else {
             cancelAutocompleteRequests();
             if (mNativeInitialized) mDropdownViewInfoListManager.recordSuggestionsShown();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
index dbcc18d..bc01b01 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
@@ -27,7 +27,6 @@
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.ui.base.Clipboard;
 import org.chromium.ui.modelutil.PropertyModel;
-import org.chromium.url.GURL;
 
 import java.util.Arrays;
 
@@ -54,12 +53,6 @@
     /** Whether the omnibox has already cleared its content for the focus event. */
     private boolean mHasClearedOmniboxForFocus;
 
-    /** The URL of the last omnibox suggestion to be processed. */
-    private GURL mLastProcessedSuggestionURL;
-
-    /** The original title of the page. */
-    private String mOriginalTitle;
-
     /**
      * @param locationBarDelegate A means of modifying the location bar.
      */
@@ -97,8 +90,6 @@
             return false;
         }
 
-        mLastProcessedSuggestionURL = suggestion.getUrl();
-
         if (!mHasClearedOmniboxForFocus) {
             mHasClearedOmniboxForFocus = true;
             mUrlBarDelegate.setOmniboxEditingText("");
@@ -120,14 +111,10 @@
     public void populateModel(AutocompleteMatch suggestion, PropertyModel model, int position) {
         super.populateModel(suggestion, model, position);
 
-        if (mOriginalTitle == null) {
-            mOriginalTitle = mTabSupplier.get().getTitle();
-        }
-
-        model.set(
-                SuggestionViewProperties.TEXT_LINE_1_TEXT, new SuggestionSpannable(mOriginalTitle));
+        model.set(SuggestionViewProperties.TEXT_LINE_1_TEXT,
+                new SuggestionSpannable(mTabSupplier.get().getTitle()));
         model.set(SuggestionViewProperties.TEXT_LINE_2_TEXT,
-                new SuggestionSpannable(mLastProcessedSuggestionURL.getSpec()));
+                new SuggestionSpannable(suggestion.getDisplayText()));
 
         setSuggestionDrawableState(model,
                 SuggestionDrawableState.Builder
@@ -151,7 +138,7 @@
                                         .setLarge(true)
                                         .setAllowTint(true)
                                         .build(),
-                                R.string.copy_link, this::onCopyLink),
+                                R.string.copy_link, () -> onCopyLink(suggestion)),
                         // TODO(https://crbug.com/1090187): do not re-use bookmark_item_edit here.
                         new Action(mContext,
                                 SuggestionDrawableState.Builder
@@ -160,15 +147,14 @@
                                         .setLarge(true)
                                         .setAllowTint(true)
                                         .build(),
-                                R.string.bookmark_item_edit, this::onEditLink)));
+                                R.string.bookmark_item_edit, () -> onEditLink(suggestion))));
 
-        fetchSuggestionFavicon(model, mLastProcessedSuggestionURL, mIconBridgeSupplier.get(), null);
+        fetchSuggestionFavicon(model, suggestion.getUrl(), mIconBridgeSupplier.get(), null);
     }
 
     @Override
     public void onUrlFocusChange(boolean hasFocus) {
         if (hasFocus) return;
-        mOriginalTitle = null;
         mHasClearedOmniboxForFocus = false;
     }
 
@@ -188,14 +174,14 @@
     }
 
     /** Invoked when user interacts with Copy action button. */
-    private void onCopyLink() {
+    private void onCopyLink(AutocompleteMatch suggestion) {
         RecordUserAction.record("Omnibox.EditUrlSuggestion.Copy");
-        Clipboard.getInstance().copyUrlToClipboard(mLastProcessedSuggestionURL);
+        Clipboard.getInstance().copyUrlToClipboard(suggestion.getUrl());
     }
 
     /** Invoked when user interacts with Edit action button. */
-    private void onEditLink() {
+    private void onEditLink(AutocompleteMatch suggestion) {
         RecordUserAction.record("Omnibox.EditUrlSuggestion.Edit");
-        mUrlBarDelegate.setOmniboxEditingText(mLastProcessedSuggestionURL.getSpec());
+        mUrlBarDelegate.setOmniboxEditingText(suggestion.getUrl().getSpec());
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinder.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinder.java
index b58ea25..0fb0dd4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinder.java
@@ -14,8 +14,9 @@
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.Log;
-import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.payments.PaymentManifestVerifier.ManifestVerifyCallback;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorSupplier;
 import org.chromium.components.payments.AndroidPaymentApp;
 import org.chromium.components.payments.ErrorStrings;
 import org.chromium.components.payments.MethodStrings;
@@ -187,9 +188,12 @@
         mPackageManagerDelegate = packageManagerDelegate;
         mTwaPackageManagerDelegate = twaPackageManagerDelegate;
         mFactory = factory;
-        ChromeActivity activity =
-                ChromeActivity.fromWebContents(mFactoryDelegate.getParams().getWebContents());
-        mIsIncognito = activity != null && activity.getCurrentTabModel().isIncognito();
+        TabModelSelector tabModelSelector = TabModelSelectorSupplier
+                                                    .from(mFactoryDelegate.getParams()
+                                                                    .getWebContents()
+                                                                    .getTopLevelNativeWindow())
+                                                    .get();
+        mIsIncognito = tabModelSelector != null && tabModelSelector.isIncognitoSelected();
     }
 
     private void findAppStoreBillingApp(List<ResolveInfo> allInstalledPaymentApps) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninFragmentBase.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninFragmentBase.java
index f091066..b71f6af 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninFragmentBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninFragmentBase.java
@@ -31,6 +31,7 @@
 import org.chromium.base.task.AsyncTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.consent_auditor.ConsentAuditorFeature;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.signin.services.DisplayableProfileData;
@@ -242,6 +243,12 @@
                 ? ProfileDataCache.createWithDefaultImageSize(
                         requireContext(), R.drawable.ic_account_child_20dp)
                 : ProfileDataCache.createWithDefaultImageSizeAndNoBadge(requireContext());
+
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.DEPRECATE_MENAGERIE_API)
+                && mSigninAccessPoint == SigninAccessPoint.START_PAGE) {
+            mProfileDataCache.disableGmsProfileDataSource();
+        }
+
         // By default this is set to true so that when system back button is pressed user action
         // is recorded in onDestroy().
         mRecordUndoSignin = true;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncErrorNotifier.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncErrorNotifier.java
index de70fe32..f7a20d5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncErrorNotifier.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncErrorNotifier.java
@@ -6,7 +6,6 @@
 
 import android.app.PendingIntent;
 import android.content.Intent;
-import android.os.Build;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
@@ -102,7 +101,7 @@
                 case PassphraseType.FROZEN_IMPLICIT_PASSPHRASE:
                 case PassphraseType.CUSTOM_PASSPHRASE:
                     showNotification(
-                            getString(R.string.sync_need_passphrase), createPassphraseIntent());
+                            getString(R.string.hint_passphrase_required), createPassphraseIntent());
                     break;
                 case PassphraseType.TRUSTED_VAULT_PASSPHRASE:
                     assert false : "Passphrase cannot be required with trusted vault passphrase";
@@ -128,20 +127,10 @@
     }
 
     /**
-     * Displays the error notification. Its title is fixed and its body is customized by the caller
-     * via errorMessage. The exact strings may depend on the Android version, to account for
-     * differences in the notification system.
+     * Displays the error notification with content |textBody|. The title of the notification is
+     * fixed.
      */
-    private void showNotification(String errorMessage, Intent intentTriggeredOnClick) {
-        String title = getString(R.string.sign_in_sync);
-        String textBody = errorMessage;
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
-            // For versions older than Android N, the notification doesn't show the app name by
-            // default, so use the app name as title.
-            title = getString(R.string.app_name);
-            textBody = getString(R.string.sign_in_sync) + ": " + errorMessage;
-        }
-
+    private void showNotification(String textBody, Intent intentTriggeredOnClick) {
         // Converting |intentTriggeredOnClick| into a PendingIntent is needed because it will be
         // handed over to the Android notification manager, a foreign application.
         // FLAG_UPDATE_CURRENT ensures any cached intent extras are updated.
@@ -164,7 +153,7 @@
                                         NotificationConstants.NOTIFICATION_ID_SYNC))
                         .setAutoCancel(true)
                         .setContentIntent(pendingIntent)
-                        .setContentTitle(title)
+                        .setContentTitle(getString(R.string.sync_error_card_title))
                         .setContentText(textBody)
                         .setSmallIcon(R.drawable.ic_chrome)
                         .setTicker(textBody)
@@ -243,8 +232,8 @@
         mTrustedVaultNotificationShownOrCreating = true;
 
         String notificationTextBody = getString(mProfileSyncService.isEncryptEverythingEnabled()
-                        ? R.string.sync_error_card_title
-                        : R.string.password_sync_error_summary);
+                        ? R.string.hint_sync_retrieve_keys_for_everything
+                        : R.string.hint_sync_retrieve_keys_for_passwords);
 
         TrustedVaultClient.get()
                 .createKeyRetrievalIntent(primaryAccountInfo)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/SyncErrorCardPreference.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/SyncErrorCardPreference.java
index eef1938a..dfa56fc0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/SyncErrorCardPreference.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/SyncErrorCardPreference.java
@@ -132,19 +132,8 @@
         } else {
             errorCardView.getStatusMessage().setVisibility(View.VISIBLE);
         }
-        if (isTrustedVaultError()) {
-            // TODO(crbug.com/1166582): For trusted vault errors, the "hint" string is already so
-            // short ("Fix now"), that the button would end up simply repeating it. So the "summary"
-            // string is used as the card description instead. In the long run, it would probably be
-            // best to make the hint string for trusted vault errors more detailed.
-            errorCardView.getDescription().setText(
-                    ProfileSyncService.get().isEncryptEverythingEnabled()
-                            ? getContext().getString(R.string.sync_error_card_title)
-                            : getContext().getString(R.string.password_sync_error_summary));
-        } else {
-            errorCardView.getDescription().setText(
-                    SyncSettingsUtils.getSyncErrorHint(getContext(), mSyncError));
-        }
+        errorCardView.getDescription().setText(
+                SyncSettingsUtils.getSyncErrorHint(getContext(), mSyncError));
 
         errorCardView.getPrimaryButton().setText(
                 SyncSettingsUtils.getSyncErrorCardButtonLabel(getContext(), mSyncError));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/SyncSettingsUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/SyncSettingsUtils.java
index 00d7e52..3ea9dd8f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/SyncSettingsUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/SyncSettingsUtils.java
@@ -139,8 +139,9 @@
             case SyncError.PASSPHRASE_REQUIRED:
                 return context.getString(R.string.hint_passphrase_required);
             case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING:
+                return context.getString(R.string.hint_sync_retrieve_keys_for_everything);
             case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS:
-                return context.getString(R.string.hint_sync_retrieve_keys);
+                return context.getString(R.string.hint_sync_retrieve_keys_for_passwords);
             case SyncError.SYNC_SETUP_INCOMPLETE:
                 return context.getString(
                         ChromeFeatureList.isEnabled(ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY)
@@ -168,7 +169,7 @@
                 return context.getString(R.string.passphrase_required_error_card_button);
             case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING:
             case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS:
-                return context.getString(R.string.hint_sync_retrieve_keys);
+                return context.getString(R.string.trusted_vault_error_card_button);
             case SyncError.SYNC_SETUP_INCOMPLETE:
                 return context.getString(R.string.sync_promo_turn_on_sync);
             case SyncError.NO_ERROR:
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
index 5541c216..5a0e384 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
@@ -114,9 +114,7 @@
 
     @Override
     public boolean shouldShowIconBeforeItem() {
-        return CachedFeatureFlags.isEnabled(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_ICONS)
-                || CachedFeatureFlags.isEnabled(
-                        ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR);
+        return true;
     }
 
     private boolean canShowDataReductionItem(int maxMenuHeight) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index f3bc95b..f710770 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -529,8 +529,7 @@
                 RecordUserAction.record("MobileShortcutFindInPage");
             }
             return true;
-        } else if (id == R.id.share_menu_button_id || id == R.id.share_menu_id
-                || id == R.id.direct_share_menu_id) {
+        } else if (id == R.id.share_menu_id || id == R.id.direct_share_menu_id) {
             onShareMenuItemSelected(id == R.id.direct_share_menu_id,
                     mTabModelSelectorSupplier.get().isIncognitoSelected());
         } else if (id == R.id.paint_preview_show_id) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webauth/IsUvpaaHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/webauth/IsUvpaaHelper.java
deleted file mode 100644
index 108742da..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/webauth/IsUvpaaHelper.java
+++ /dev/null
@@ -1,63 +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.
-
-package org.chromium.chrome.browser.webauth;
-
-import android.content.Context;
-import android.os.Build;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.PackageUtils;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-
-/**
- * This class provides a separate entry point to the WebAuthentication
- * implementation that allows IsUserVerifyingPlatformAuthenticator to be called
- * without a RenderFrameHost. It exists to support IsUVPAA metrics.
- * This is intended to be used from native code.
- */
-@JNINamespace("webauth")
-public class IsUvpaaHelper {
-    private static final String GMSCORE_PACKAGE_NAME = "com.google.android.gms";
-
-    /**
-     * Determine whether a user-verifying platform authenticator is available
-     * for WebAuthn.
-     */
-    @CalledByNative
-    public static void isUserVerifyingPlatformAuthenticatorAvailable() {
-        Context context = ContextUtils.getApplicationContext();
-        if (context == null) {
-            IsUvpaaHelperJni.get().onIsUvpaaComplete(false);
-            return;
-        }
-
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
-            IsUvpaaHelperJni.get().onIsUvpaaComplete(false);
-            return;
-        }
-
-        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_AUTH)) {
-            IsUvpaaHelperJni.get().onIsUvpaaComplete(false);
-            return;
-        }
-
-        if (PackageUtils.getPackageVersion(context, GMSCORE_PACKAGE_NAME)
-                < Fido2ApiHandler.GMSCORE_MIN_VERSION) {
-            IsUvpaaHelperJni.get().onIsUvpaaComplete(false);
-            return;
-        }
-
-        Fido2ApiHandler.getInstance().isUserVerifyingPlatformAuthenticatorAvailable(
-                null, isUVPAA -> IsUvpaaHelperJni.get().onIsUvpaaComplete(isUVPAA));
-    }
-
-    @NativeMethods
-    interface Natives {
-        void onIsUvpaaComplete(boolean available);
-    }
-}
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java
index 5682b316c..62bb587 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java
@@ -143,9 +143,10 @@
                 int itemId = item.getItemId();
                 assertTrue(itemId == R.id.new_tab_menu_id
                         || itemId == R.id.new_incognito_tab_menu_id
-                        || itemId == R.id.all_bookmarks_menu_id
-                        || itemId == R.id.recent_tabs_menu_id || itemId == R.id.open_history_menu_id
-                        || itemId == R.id.downloads_menu_id || itemId == R.id.close_all_tabs_menu_id
+                        || itemId == R.id.divider_line_id || itemId == R.id.open_history_menu_id
+                        || itemId == R.id.downloads_menu_id || itemId == R.id.all_bookmarks_menu_id
+                        || itemId == R.id.recent_tabs_menu_id
+                        || itemId == R.id.close_all_tabs_menu_id
                         || itemId == R.id.close_all_incognito_tabs_menu_id
                         || itemId == R.id.menu_group_tabs || itemId == R.id.track_prices_row_menu_id
                         || itemId == R.id.preferences_id);
@@ -158,7 +159,7 @@
                 checkedMenuItems++;
             }
         }
-        assertThat(checkedMenuItems, equalTo(11));
+        assertThat(checkedMenuItems, equalTo(13));
     }
 
     @Test
@@ -181,9 +182,10 @@
                 int itemId = item.getItemId();
                 assertTrue(itemId == R.id.new_tab_menu_id
                         || itemId == R.id.new_incognito_tab_menu_id
-                        || itemId == R.id.all_bookmarks_menu_id
-                        || itemId == R.id.recent_tabs_menu_id || itemId == R.id.open_history_menu_id
-                        || itemId == R.id.downloads_menu_id || itemId == R.id.close_all_tabs_menu_id
+                        || itemId == R.id.divider_line_id || itemId == R.id.open_history_menu_id
+                        || itemId == R.id.downloads_menu_id || itemId == R.id.all_bookmarks_menu_id
+                        || itemId == R.id.recent_tabs_menu_id
+                        || itemId == R.id.close_all_tabs_menu_id
                         || itemId == R.id.close_all_incognito_tabs_menu_id
                         || itemId == R.id.menu_group_tabs || itemId == R.id.track_prices_row_menu_id
                         || itemId == R.id.preferences_id);
@@ -196,7 +198,7 @@
                 checkedMenuItems++;
             }
         }
-        assertThat(checkedMenuItems, equalTo(11));
+        assertThat(checkedMenuItems, equalTo(13));
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/TabbedAppMenuTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/TabbedAppMenuTest.java
index 25ffa0f..6401d8c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/TabbedAppMenuTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/TabbedAppMenuTest.java
@@ -247,11 +247,9 @@
     @Test
     @SmallTest
     @Feature({"Browser", "Main", "Bookmark", "RenderTest"})
-    @DisableFeatures({ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP,
-            ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_ICONS})
+    @DisableFeatures({ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR})
     @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
-    public void
-    testBookmarkMenuItem() throws IOException {
+    public void testBookmarkMenuItem() throws IOException {
         MenuItem bookmarkStar =
                 AppMenuTestSupport.getMenu(mActivityTestRule.getAppMenuCoordinator())
                         .findItem(R.id.bookmark_this_page_id);
@@ -259,7 +257,7 @@
         Assert.assertEquals("Incorrect content description.",
                 mActivityTestRule.getActivity().getString(R.string.menu_bookmark),
                 bookmarkStar.getTitleCondensed());
-        mRenderTestRule.render(getListView().getChildAt(0), "icon_row");
+        mRenderTestRule.render(getListView().getChildAt(0), "rounded_corner_icon_row");
 
         TestThreadUtils.runOnUiThreadBlocking(() -> mAppMenuHandler.hideAppMenu());
         AppMenuPropertiesDelegateImpl.setPageBookmarkedForTesting(true);
@@ -272,7 +270,8 @@
         Assert.assertEquals("Incorrect content description for bookmarked page.",
                 mActivityTestRule.getActivity().getString(R.string.edit_bookmark),
                 bookmarkStar.getTitleCondensed());
-        mRenderTestRule.render(getListView().getChildAt(0), "icon_row_page_bookmarked");
+        mRenderTestRule.render(
+                getListView().getChildAt(0), "rounded_corner_icon_row_page_bookmarked");
 
         AppMenuPropertiesDelegateImpl.setPageBookmarkedForTesting(null);
     }
@@ -288,7 +287,8 @@
     testActionChipViewMenuItem() throws IOException {
         LinearLayout actionBar = (LinearLayout) getListView().getChildAt(0);
         Assert.assertEquals(3, actionBar.getChildCount());
-        mRenderTestRule.render(getListView().getChildAt(0), "tinted_icon_row_three_buttons");
+        mRenderTestRule.render(
+                getListView().getChildAt(0), "tinted_rounded_corner_icon_row_three_buttons");
 
         int downloadRowIndex = findIndexOfMenuItemById(R.id.downloads_row_menu_id);
         Assert.assertNotEquals("No download row found.", -1, downloadRowIndex);
@@ -330,7 +330,8 @@
     testDestinationChipViewMenuItem() throws IOException {
         LinearLayout actionBar = (LinearLayout) getListView().getChildAt(0);
         Assert.assertEquals(3, actionBar.getChildCount());
-        mRenderTestRule.render(getListView().getChildAt(0), "tinted_icon_row_three_buttons");
+        mRenderTestRule.render(
+                getListView().getChildAt(0), "tinted_rounded_corner_icon_row_three_buttons");
 
         int downloadRowIndex = findIndexOfMenuItemById(R.id.downloads_row_menu_id);
         Assert.assertNotEquals("No download row found.", -1, downloadRowIndex);
@@ -372,7 +373,8 @@
     testAddToMenuItem_not_bookmarked() throws IOException {
         LinearLayout actionBar = (LinearLayout) getListView().getChildAt(0);
         Assert.assertEquals(3, actionBar.getChildCount());
-        mRenderTestRule.render(getListView().getChildAt(0), "tinted_icon_row_three_buttons");
+        mRenderTestRule.render(
+                getListView().getChildAt(0), "tinted_rounded_corner_icon_row_three_buttons");
 
         int addToIndex = findIndexOfMenuItemById(R.id.add_to_menu_id);
         Assert.assertNotEquals("No add to row found.", -1, addToIndex);
@@ -408,7 +410,8 @@
         LinearLayout actionBar = (LinearLayout) getListView().getChildAt(0);
         Assert.assertEquals("Add to Bookmarks/Downloads/Home screen should be shown", 3,
                 actionBar.getChildCount());
-        mRenderTestRule.render(getListView().getChildAt(0), "tinted_icon_row_three_buttons");
+        mRenderTestRule.render(
+                getListView().getChildAt(0), "tinted_rounded_corner_icon_row_three_buttons");
 
         int addToIndex = findIndexOfMenuItemById(R.id.add_to_menu_id);
         Assert.assertNotEquals("No add to row found.", -1, addToIndex);
@@ -433,7 +436,6 @@
     @Test
     @SmallTest
     @Feature({"Browser", "Main", "RenderTest"})
-    @EnableFeatures({ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP})
     public void testDividerLineMenuItem() throws IOException {
         int firstDividerLineIndex = findIndexOfMenuItemById(R.id.divider_line_id);
         Assert.assertTrue("No divider line found.", firstDividerLineIndex != -1);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SyncErrorInfoBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SyncErrorInfoBarTest.java
index 3fbbfa1..cc1166f1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SyncErrorInfoBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SyncErrorInfoBarTest.java
@@ -50,7 +50,7 @@
 
     @Rule
     public final ChromeRenderTestRule mRenderTestRule =
-            ChromeRenderTestRule.Builder.withPublicCorpus().build();
+            ChromeRenderTestRule.Builder.withPublicCorpus().setRevision(1).build();
 
     @Before
     public void setUp() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarLayoutTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarLayoutTest.java
index 3bcbe1d..9218154 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarLayoutTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarLayoutTest.java
@@ -12,10 +12,8 @@
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 
-import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 
@@ -48,14 +46,11 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.dom_distiller.DomDistillerTabUtils;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
-import org.chromium.chrome.browser.lens.LensController;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.toolbar.LocationBarModel;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.OmniboxTestUtils;
-import org.chromium.chrome.test.util.ViewUtils;
-import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.security_state.ConnectionSecurityLevel;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.ClickUtils;
@@ -85,8 +80,6 @@
     AndroidPermissionDelegate mAndroidPermissionDelegate;
     @Mock
     SearchEngineLogoUtils mSearchEngineLogoUtils;
-    @Mock
-    LensController mLensController;
 
     private TestLocationBarModel mTestLocationBarModel;
 
@@ -147,7 +140,6 @@
 
     @Before
     public void setUp() throws InterruptedException {
-        LensController.setInstanceForTesting(mLensController);
         mActivityTestRule.startMainActivityOnBlankPage();
         setupModelsForCurrentTab();
 
@@ -198,10 +190,6 @@
         return mActivityTestRule.getActivity().findViewById(R.id.mic_button);
     }
 
-    private ImageButton getLensButton() {
-        return mActivityTestRule.getActivity().findViewById(R.id.lens_camera_button);
-    }
-
     private View getStatusIconView() {
         return mActivityTestRule.getActivity().findViewById(R.id.location_bar_status_icon);
     }
@@ -248,65 +236,6 @@
 
     @Test
     @SmallTest
-    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
-    public void testLensButtonVisibilityOnStartNtp_enabled() throws ExecutionException {
-        doReturn(true).when(mLensController).isLensEnabled(any());
-        loadUrlInNewTabAndUpdateModels(UrlConstants.NTP_URL, false);
-        ViewUtils.waitForView(allOf(withId(R.id.lens_camera_button), isDisplayed()));
-    }
-
-    @Test
-    @SmallTest
-    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
-    public void testLensButtonVisibilityOnStartNtp_disabled() throws ExecutionException {
-        doReturn(false).when(mLensController).isLensEnabled(any());
-        loadUrlInNewTabAndUpdateModels(UrlConstants.NTP_URL, false);
-        ViewUtils.waitForView(allOf(withId(R.id.lens_camera_button), not(isDisplayed())));
-    }
-
-    @Test
-    @SmallTest
-    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
-    public void testNotShowingLensButtonIfUrlBarContainsText() throws ExecutionException {
-        doReturn(true).when(mLensController).isLensEnabled(any());
-        loadUrlInNewTabAndUpdateModels("", false);
-        // When there is text, the delete button should be visible.
-        setUrlBarTextAndFocus("testing");
-        onView(withId(R.id.delete_button)).check(matches(isDisplayed()));
-        onView(withId(R.id.lens_camera_button)).check(matches(not(isDisplayed())));
-    }
-
-    @Test
-    @SmallTest
-    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
-    public void testShowingLensButtonIfUrlBarIsEmpty() throws ExecutionException {
-        doReturn(true).when(mLensController).isLensEnabled(any());
-        loadUrlInNewTabAndUpdateModels("", false);
-        // When there's no text, the lens button should be visible.
-        setUrlBarTextAndFocus("");
-        onView(withId(R.id.delete_button)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.lens_camera_button)).check(matches(isDisplayed()));
-    }
-
-    @Test
-    @SmallTest
-    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
-    public void testShowingLensButtonAfterTextDeleted() throws ExecutionException {
-        doReturn(true).when(mLensController).isLensEnabled(any());
-        loadUrlInNewTabAndUpdateModels("", false);
-        // When there is text, the delete button should be visible.
-        setUrlBarTextAndFocus("testing");
-        onView(withId(R.id.delete_button)).check(matches(isDisplayed()));
-        onView(withId(R.id.lens_camera_button)).check(matches(not(isDisplayed())));
-        // When there's no text, the lens button should be visible.
-        ClickUtils.clickButton(getDeleteButton());
-        onView(withId(R.id.delete_button)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.lens_camera_button)).check(matches(isDisplayed()));
-        Assert.assertEquals("", getUrlText(getUrlBar()));
-    }
-
-    @Test
-    @SmallTest
     public void testDeleteButton() throws ExecutionException {
         setUrlBarTextAndFocus("testing");
         Assert.assertEquals(getDeleteButton().getVisibility(), VISIBLE);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
index b8ae62f0..b56d83e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
@@ -21,6 +21,8 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.view.View;
+import android.widget.LinearLayout;
 
 import androidx.lifecycle.Lifecycle;
 import androidx.test.espresso.matcher.ViewMatchers;
@@ -196,6 +198,19 @@
                 .getSearchEngineLogoFavicon(any(), any(), any(), any());
     }
 
+    private void assertTheLastVisibleButtonInSearchBoxById(int id) {
+        LinearLayout urlActionContainer =
+                mActivityTestRule.getActivity().findViewById(R.id.url_action_container);
+
+        for (int i = urlActionContainer.getChildCount() - 1; i >= 0; i--) {
+            if (urlActionContainer.getChildAt(i).getVisibility() != View.VISIBLE) {
+                continue;
+            }
+            Assert.assertEquals(urlActionContainer.getChildAt(i).getId(), id);
+            break;
+        }
+    }
+
     @Test
     @MediumTest
     public void testSetSearchQueryFocusesUrlBar() {
@@ -377,27 +392,65 @@
     @Test
     @MediumTest
     @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
-    public void testFocusLogic_cameraAssistedSearchLenButtonVisibilityPhone() {
+    public void testFocusLogic_cameraAssistedSearchLenButtonVisibilityPhone_lensDisabled() {
         startActivityNormally();
+        doReturn(true).when(mVoiceRecognitionHandler).isVoiceSearchEnabled();
+        doReturn(false).when(mLensController).isLensEnabled(any());
+        String url = mActivityTestRule.getEmbeddedTestServerRule().getServer().getURLWithHostName(
+                HOSTNAME, "/");
+        mActivityTestRule.loadUrl(url);
+
+        onView(withId(R.id.url_action_container)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.lens_camera_button_end)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.lens_camera_button_start)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.delete_button)).check(matches(not(isDisplayed())));
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mUrlBar.requestFocus(); });
+
+        ViewUtils.waitForView(allOf(withId(R.id.url_action_container), isDisplayed()));
+        onView(withId(R.id.lens_camera_button_end)).check((matches(not(isDisplayed()))));
+        onView(withId(R.id.lens_camera_button_start)).check((matches(not(isDisplayed()))));
+        onView(withId(R.id.delete_button)).check(matches(not(isDisplayed())));
+        assertTheLastVisibleButtonInSearchBoxById(R.id.mic_button);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mLocationBarCoordinator.setOmniboxEditingText(url); });
+
+        onView(withId(R.id.lens_camera_button_end)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.lens_camera_button_start)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.delete_button)).check(matches(isDisplayed()));
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mUrlBar.clearFocus(); });
+
+        ViewUtils.waitForView(allOf(withId(R.id.url_action_container), not(isDisplayed())));
+    }
+
+    @Test
+    @MediumTest
+    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
+    public void testFocusLogic_cameraAssistedSearchLenButtonVisibilityPhone_lensEnabled() {
+        startActivityNormally();
+        doReturn(true).when(mVoiceRecognitionHandler).isVoiceSearchEnabled();
         doReturn(true).when(mLensController).isLensEnabled(any());
         String url = mActivityTestRule.getEmbeddedTestServerRule().getServer().getURLWithHostName(
                 HOSTNAME, "/");
         mActivityTestRule.loadUrl(url);
 
         onView(withId(R.id.url_action_container)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.lens_camera_button)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.lens_camera_button_end)).check(matches(not(isDisplayed())));
         onView(withId(R.id.delete_button)).check(matches(not(isDisplayed())));
 
         TestThreadUtils.runOnUiThreadBlocking(() -> { mUrlBar.requestFocus(); });
 
         ViewUtils.waitForView(allOf(withId(R.id.url_action_container), isDisplayed()));
-        onView(withId(R.id.lens_camera_button)).check((matches(isDisplayed())));
+        onView(withId(R.id.lens_camera_button_end)).check((matches(isDisplayed())));
         onView(withId(R.id.delete_button)).check(matches(not(isDisplayed())));
+        assertTheLastVisibleButtonInSearchBoxById(R.id.lens_camera_button_end);
 
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> { mLocationBarCoordinator.setOmniboxEditingText(url); });
 
-        onView(withId(R.id.lens_camera_button)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.lens_camera_button_end)).check(matches(not(isDisplayed())));
         onView(withId(R.id.delete_button)).check(matches(isDisplayed()));
 
         TestThreadUtils.runOnUiThreadBlocking(() -> { mUrlBar.clearFocus(); });
@@ -407,6 +460,85 @@
 
     @Test
     @MediumTest
+    @CommandLineFlags.
+    Add({"enable-features=" + ChromeFeatureList.LENS_CAMERA_ASSISTED_SEARCH + "<FakeStudyName",
+            "force-fieldtrials=FakeStudyName/Enabled",
+            "force-fieldtrial-params=FakeStudyName.Enabled:"
+                    + "searchBoxStartVariantForLensCameraAssistedSearch/true"})
+    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
+    public void
+    testFocusLogic_lenButtonOnTheStartOfSearchBoxBtnGroupVisibilityPhone() {
+        startActivityNormally();
+        doReturn(true).when(mVoiceRecognitionHandler).isVoiceSearchEnabled();
+        doReturn(true).when(mLensController).isLensEnabled(any());
+        String url = mActivityTestRule.getEmbeddedTestServerRule().getServer().getURLWithHostName(
+                HOSTNAME, "/");
+        mActivityTestRule.loadUrl(url);
+
+        onView(withId(R.id.url_action_container)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.lens_camera_button_end)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.lens_camera_button_start)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.delete_button)).check(matches(not(isDisplayed())));
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mUrlBar.requestFocus(); });
+
+        ViewUtils.waitForView(allOf(withId(R.id.url_action_container), isDisplayed()));
+        onView(withId(R.id.lens_camera_button_end)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.lens_camera_button_start)).check((matches(isDisplayed())));
+        onView(withId(R.id.delete_button)).check(matches(not(isDisplayed())));
+        assertTheLastVisibleButtonInSearchBoxById(R.id.mic_button);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mLocationBarCoordinator.setOmniboxEditingText(url); });
+
+        onView(withId(R.id.lens_camera_button_end)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.lens_camera_button_start)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.delete_button)).check(matches(isDisplayed()));
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mUrlBar.clearFocus(); });
+
+        ViewUtils.waitForView(allOf(withId(R.id.url_action_container), not(isDisplayed())));
+    }
+
+    @Test
+    @MediumTest
+    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
+    public void testFocusLogic_lenButtonVisibilityOnStartNtpPhone() {
+        startActivityNormally();
+        doReturn(true).when(mVoiceRecognitionHandler).isVoiceSearchEnabled();
+        doReturn(true).when(mLensController).isLensEnabled(any());
+
+        mActivityTestRule.loadUrl(UrlConstants.NTP_URL);
+
+        ViewUtils.waitForView(allOf(withId(R.id.mic_button), isDisplayed()));
+        ViewUtils.waitForView(allOf(withId(R.id.lens_camera_button_end), isDisplayed()));
+        ViewUtils.waitForView(allOf(withId(R.id.lens_camera_button_start), not(isDisplayed())));
+        assertTheLastVisibleButtonInSearchBoxById(R.id.lens_camera_button_end);
+    }
+
+    @Test
+    @MediumTest
+    @CommandLineFlags.
+    Add({"enable-features=" + ChromeFeatureList.LENS_CAMERA_ASSISTED_SEARCH + "<FakeStudyName",
+            "force-fieldtrials=FakeStudyName/Enabled",
+            "force-fieldtrial-params=FakeStudyName.Enabled:"
+                    + "searchBoxStartVariantForLensCameraAssistedSearch/true"})
+    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
+    public void
+    testFocusLogic_lenButtonOnTheStartOfSearchBoxBtnGroupVisibilityOnStartNtpPhone() {
+        startActivityNormally();
+        doReturn(true).when(mVoiceRecognitionHandler).isVoiceSearchEnabled();
+        doReturn(true).when(mLensController).isLensEnabled(any());
+        mActivityTestRule.loadUrl(UrlConstants.NTP_URL);
+
+        ViewUtils.waitForView(allOf(withId(R.id.mic_button), isDisplayed()));
+        ViewUtils.waitForView(allOf(withId(R.id.lens_camera_button_end), not(isDisplayed())));
+        ViewUtils.waitForView(allOf(withId(R.id.lens_camera_button_start), isDisplayed()));
+        assertTheLastVisibleButtonInSearchBoxById(R.id.mic_button);
+    }
+
+    @Test
+    @MediumTest
     @Restriction(UiRestriction.RESTRICTION_TYPE_TABLET)
     public void testFocusLogic_buttonVisibilityTablet() {
         startActivityNormally();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
index 4e1fb10..5fa1803 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
@@ -9,6 +9,8 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -68,6 +70,8 @@
     @Mock
     UrlBarEditingTextStateProvider mUrlBarEditingTextStateProvider;
     @Mock
+    Runnable mMockForceModelViewReconciliationRunnable;
+    @Mock
     SearchEngineLogoUtils mSearchEngineLogoUtils;
     @Mock
     Profile mProfile;
@@ -107,14 +111,13 @@
                 .when(mSearchEngineLogoUtils)
                 .getSearchEngineLogoFavicon(any(), eq(mResources), any(), any());
 
-        // clang-format off
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mMediator = new StatusMediator(mModel, mResources, mContext,
-                    mUrlBarEditingTextStateProvider, /* isTablet */ false, () -> {},
+                    mUrlBarEditingTextStateProvider,
+                    /* isTablet */ false, mMockForceModelViewReconciliationRunnable,
                     mLocationBarDataProvider, mPermissionDialogController, mSearchEngineLogoUtils,
                     () -> mTemplateUrlService, () -> mProfile, null);
         });
-        // clang-format on
         mBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
     }
 
@@ -461,6 +464,49 @@
 
     @Test
     @SmallTest
+    @EnableFeatures(ChromeFeatureList.OMNIBOX_SEARCH_ENGINE_LOGO)
+    @UiThreadTest
+    public void testIncognitoStateChange_goingToIncognito() {
+        mMediator.setShowIconsWhenUrlFocused(true);
+
+        setupSearchEngineLogoForTesting(
+                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
+        doReturn(true).when(mLocationBarDataProvider).isIncognito();
+        mMediator.onIncognitoStateChanged();
+        verify(mMockForceModelViewReconciliationRunnable, times(0)).run();
+    }
+
+    @Test
+    @SmallTest
+    @EnableFeatures(ChromeFeatureList.OMNIBOX_SEARCH_ENGINE_LOGO)
+    @UiThreadTest
+    public void testIncognitoStateChange_backFromIncognito() {
+        mMediator.setShowIconsWhenUrlFocused(true);
+
+        setupSearchEngineLogoForTesting(
+                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
+        doReturn(true).when(mLocationBarDataProvider).isIncognito();
+        mMediator.onIncognitoStateChanged();
+        doReturn(false).when(mLocationBarDataProvider).isIncognito();
+        mMediator.onIncognitoStateChanged();
+        verify(mMockForceModelViewReconciliationRunnable).run();
+    }
+
+    @Test
+    @SmallTest
+    @EnableFeatures(ChromeFeatureList.OMNIBOX_SEARCH_ENGINE_LOGO)
+    @UiThreadTest
+    public void testIncognitoStateChange_shouldShowStatusIcon() {
+        mMediator.setShowIconsWhenUrlFocused(true);
+        doReturn(true).when(mLocationBarDataProvider).isIncognito();
+        mMediator.onIncognitoStateChanged();
+        doReturn(false).when(mLocationBarDataProvider).isIncognito();
+        mMediator.onIncognitoStateChanged();
+        verify(mMockForceModelViewReconciliationRunnable, times(0)).run();
+    }
+
+    @Test
+    @SmallTest
     @UiThreadTest
     public void testStatusText() {
         mMediator.setUnfocusedLocationBarWidth(10);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionUnitTest.java
index be2423f..a469d67 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionUnitTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionUnitTest.java
@@ -156,6 +156,7 @@
         doReturn(mUserDataHost).when(mTab).getUserDataHost();
         doReturn(false).when(mSadTab).isShowing();
         doReturn(OmniboxSuggestionType.URL_WHAT_YOU_TYPED).when(mWhatYouTypedSuggestion).getType();
+        doReturn(mTestUrl.getSpec()).when(mWhatYouTypedSuggestion).getDisplayText();
         doReturn(mTestUrl).when(mWhatYouTypedSuggestion).getUrl();
 
         doReturn(OmniboxSuggestionType.SEARCH_WHAT_YOU_TYPED).when(mSearchSuggestion).getType();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java
index a9ce6934..eac373d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java
@@ -56,6 +56,8 @@
     @Mock
     UrlBarEditingTextStateProvider mUrlBarEditingTextStateProvider;
     @Mock
+    Runnable mMockForceModelViewReconciliationRunnable;
+    @Mock
     SearchEngineLogoUtils mSearchEngineLogoUtils;
     @Mock
     Profile mProfile;
@@ -77,14 +79,13 @@
         mModel = new PropertyModel(StatusProperties.ALL_KEYS);
         mPermissionDialogController = PermissionDialogController.getInstance();
 
-        // clang-format off
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mMediator = new StatusMediator(mModel, mResources, mContext,
-                    mUrlBarEditingTextStateProvider, /* isTablet */ false,
-                    () -> {}, mLocationBarDataProvider, mPermissionDialogController,
-                    mSearchEngineLogoUtils, () -> mTemplateUrlService, () -> mProfile, null);
+                    mUrlBarEditingTextStateProvider,
+                    /* isTablet */ false, mMockForceModelViewReconciliationRunnable,
+                    mLocationBarDataProvider, mPermissionDialogController, mSearchEngineLogoUtils,
+                    () -> mTemplateUrlService, () -> mProfile, null);
         });
-        // clang-format on
     }
 
     @Test
@@ -132,4 +133,4 @@
 
         Assert.assertEquals(ContentSettingsType.DEFAULT, mMediator.getLastPermission());
     }
-}
+}
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java
index 42af63a..c8b93076 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java
@@ -28,6 +28,8 @@
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.UiThreadTest;
 import org.chromium.base.test.util.Batch;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorSupplier;
 import org.chromium.chrome.test.ChromeBrowserTestRule;
 import org.chromium.components.payments.PackageManagerDelegate;
 import org.chromium.components.payments.PaymentApp;
@@ -40,6 +42,7 @@
 import org.chromium.content_public.browser.test.NativeLibraryTestUtils;
 import org.chromium.payments.mojom.PaymentDetailsModifier;
 import org.chromium.payments.mojom.PaymentMethodData;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.url.GURL;
 import org.chromium.url.Origin;
 
@@ -108,7 +111,13 @@
             methodData.put(methodName, data);
         }
         PaymentAppFactoryParams params = Mockito.mock(PaymentAppFactoryParams.class);
-        Mockito.when(params.getWebContents()).thenReturn(Mockito.mock(WebContents.class));
+        WebContents webContents = Mockito.mock(WebContents.class);
+        WindowAndroid windowAndroid = Mockito.mock(WindowAndroid.class);
+        TabModelSelector tabModelSelector = Mockito.mock(TabModelSelector.class);
+        TabModelSelectorSupplier.setInstanceForTesting(tabModelSelector);
+        Mockito.when(tabModelSelector.isIncognitoSelected()).thenReturn(false);
+        Mockito.when(webContents.getTopLevelNativeWindow()).thenReturn(windowAndroid);
+        Mockito.when(params.getWebContents()).thenReturn(webContents);
         Mockito.when(params.getId()).thenReturn("id");
         Mockito.when(params.getMethodData()).thenReturn(methodData);
         Mockito.when(params.getTopLevelOrigin()).thenReturn("https://chromium.org");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
index 4ed0ec46..f84d1a8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
@@ -69,7 +69,7 @@
 public class ManageSyncSettingsTest {
     private static final String TAG = "ManageSyncSettingsTest";
 
-    private static final int RENDER_TEST_REVISION = 2;
+    private static final int RENDER_TEST_REVISION = 3;
 
     /**
      * Maps ModelTypes to their UI element IDs.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncErrorCardPreferenceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncErrorCardPreferenceTest.java
index ef85df6..0ec8a04 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncErrorCardPreferenceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/SyncErrorCardPreferenceTest.java
@@ -62,7 +62,7 @@
 
     @Rule
     public final ChromeRenderTestRule mRenderTestRule =
-            ChromeRenderTestRule.Builder.withPublicCorpus().setRevision(3).build();
+            ChromeRenderTestRule.Builder.withPublicCorpus().setRevision(4).build();
 
     private FakeProfileSyncService mFakeProfileSyncService;
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/adaptive/OptionalNewTabButtonControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/adaptive/OptionalNewTabButtonControllerTest.java
index 6a7789f2..5f55960 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/adaptive/OptionalNewTabButtonControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/adaptive/OptionalNewTabButtonControllerTest.java
@@ -29,6 +29,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.UserActionTester;
 import org.chromium.chrome.R;
@@ -40,10 +41,11 @@
 import org.chromium.chrome.test.util.ViewUtils;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.components.embedder_support.util.UrlConstants;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /** Tests {@link OptionalNewTabButtonController}. */
 @RunWith(ChromeJUnit4ClassRunner.class)
-// @Batch(Batch.PER_CLASS)
+@Batch(Batch.PER_CLASS)
 @EnableFeatures({ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR})
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
         "enable-features=" + ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR + "<Study",
@@ -57,16 +59,13 @@
 
     @Rule
     public final BlankCTATabInitialStateRule mInitialStateRule =
-            new BlankCTATabInitialStateRule(sActivityTestRule, true);
+            new BlankCTATabInitialStateRule(sActivityTestRule, /*clearAllTabState=*/false);
 
     private String mTestPageUrl;
     private String mButtonDescription;
 
     @Before
     public void setUp() {
-        AdaptiveToolbarFeatures.MODE_PARAM.setForTesting(AdaptiveToolbarFeatures.ALWAYS_NEW_TAB);
-        AdaptiveToolbarFeatures.clearParsedParamsForTesting();
-
         mTestPageUrl = sActivityTestRule.getTestServer().getURL(TEST_PAGE);
         mButtonDescription =
                 sActivityTestRule.getActivity().getResources().getString(R.string.button_new_tab);
@@ -75,17 +74,22 @@
     @Test
     @MediumTest
     public void testClick_opensNewTab() {
-        sActivityTestRule.loadUrl(mTestPageUrl);
+        sActivityTestRule.loadUrl(mTestPageUrl, /*secondsToWait=*/10);
 
         onViewWaiting(allOf(withId(R.id.optional_toolbar_button), isDisplayed(), isEnabled(),
                               withContentDescription(mButtonDescription)))
                 .perform(click());
 
-        assertEquals(2,
-                sActivityTestRule.getActivity()
-                        .getCurrentTabModel()
-                        .getComprehensiveModel()
-                        .getCount());
+        // Expected tabs:
+        // 1: mTestPageUrl
+        // 2: opened by the click
+        assertEquals(Integer.valueOf(2),
+                TestThreadUtils.<Integer>runOnUiThreadBlockingNoException(() -> {
+                    return sActivityTestRule.getActivity()
+                            .getCurrentTabModel()
+                            .getComprehensiveModel()
+                            .getCount();
+                }));
     }
 
     @Test
@@ -97,21 +101,28 @@
                               withContentDescription(mButtonDescription)))
                 .perform(click());
 
-        assertEquals(2,
-                sActivityTestRule.getActivity()
-                        .getCurrentTabModel()
-                        .getComprehensiveModel()
-                        .getCount());
-        assertTrue(sActivityTestRule.getActivity()
-                           .getTabModelSelectorSupplier()
-                           .get()
-                           .isIncognitoSelected());
+        // Expected tabs:
+        // 1: mTestPageUrl
+        // 2: opened by the click
+        assertEquals(Integer.valueOf(2),
+                TestThreadUtils.<Integer>runOnUiThreadBlockingNoException(() -> {
+                    return sActivityTestRule.getActivity()
+                            .getCurrentTabModel()
+                            .getComprehensiveModel()
+                            .getCount();
+                }));
+        assertTrue(TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            return sActivityTestRule.getActivity()
+                    .getTabModelSelectorSupplier()
+                    .get()
+                    .isIncognitoSelected();
+        }));
     }
 
     @Test
     @MediumTest
     public void testClick_recordsUserAction() {
-        sActivityTestRule.loadUrl(mTestPageUrl);
+        sActivityTestRule.loadUrl(mTestPageUrl, /*secondsToWait=*/10);
         UserActionTester userActionTester = new UserActionTester();
 
         onViewWaiting(allOf(withId(R.id.optional_toolbar_button), isDisplayed(), isEnabled(),
@@ -125,7 +136,7 @@
     @Test
     @MediumTest
     public void testButton_hidesOnNTP() {
-        sActivityTestRule.loadUrl(mTestPageUrl);
+        sActivityTestRule.loadUrl(mTestPageUrl, /*secondsToWait=*/10);
         onViewWaiting(allOf(withId(R.id.optional_toolbar_button), isDisplayed(), isEnabled(),
                 withContentDescription(mButtonDescription)));
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
index aa390b9..b44b56fd 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
@@ -246,9 +246,10 @@
         mAppMenuPropertiesDelegate.prepareMenu(menu, null);
 
         Integer[] expectedItems = {R.id.icon_row_menu_id, R.id.new_tab_menu_id,
-                R.id.new_incognito_tab_menu_id, R.id.all_bookmarks_menu_id,
-                R.id.recent_tabs_menu_id, R.id.open_history_menu_id, R.id.downloads_menu_id,
-                R.id.feed_follow_id, R.id.request_desktop_site_row_menu_id, R.id.preferences_id,
+                R.id.new_incognito_tab_menu_id, R.id.divider_line_id, R.id.open_history_menu_id,
+                R.id.downloads_row_menu_id, R.id.all_bookmarks_row_menu_id,
+                R.id.recent_tabs_menu_id, R.id.divider_line_id, R.id.feed_follow_id,
+                R.id.request_desktop_site_row_menu_id, R.id.divider_line_id, R.id.preferences_id,
                 R.id.help_id};
         assertMenuItemsAreEqual(menu, expectedItems);
     }
@@ -266,16 +267,16 @@
         mAppMenuPropertiesDelegate.prepareMenu(menu, null);
 
         Integer[] expectedItems = {R.id.icon_row_menu_id, R.id.new_tab_menu_id,
-                R.id.new_incognito_tab_menu_id, R.id.all_bookmarks_menu_id,
-                R.id.recent_tabs_menu_id, R.id.open_history_menu_id, R.id.downloads_menu_id,
-                R.id.translate_id, R.id.share_row_menu_id, R.id.feed_follow_id,
-                R.id.find_in_page_id, R.id.add_to_homescreen_id,
-                R.id.request_desktop_site_row_menu_id, R.id.preferences_id, R.id.help_id};
-        Integer[] expectedTitles = {0, R.string.menu_new_tab, R.string.menu_new_incognito_tab,
-                R.string.menu_bookmarks, R.string.menu_recent_tabs, R.string.menu_history,
-                R.string.menu_downloads, R.string.menu_translate, 0, R.string.menu_follow,
-                R.string.menu_find_in_page, R.string.menu_add_to_homescreen, 0,
-                R.string.menu_settings, R.string.menu_help};
+                R.id.new_incognito_tab_menu_id, R.id.divider_line_id, R.id.open_history_menu_id,
+                R.id.downloads_row_menu_id, R.id.all_bookmarks_row_menu_id,
+                R.id.recent_tabs_menu_id, R.id.divider_line_id, R.id.share_row_menu_id,
+                R.id.feed_follow_id, R.id.find_in_page_id, R.id.translate_id,
+                R.id.add_to_homescreen_id, R.id.request_desktop_site_row_menu_id,
+                R.id.divider_line_id, R.id.preferences_id, R.id.help_id};
+        Integer[] expectedTitles = {0, R.string.menu_new_tab, R.string.menu_new_incognito_tab, 0,
+                R.string.menu_history, 0, 0, R.string.menu_recent_tabs, 0, 0, R.string.menu_follow,
+                R.string.menu_find_in_page, R.string.menu_translate,
+                R.string.menu_add_to_homescreen, 0, 0, R.string.menu_settings, R.string.menu_help};
         Integer[] expectedActionBarItems = {R.id.forward_menu_id, R.id.bookmark_this_page_id,
                 R.id.offline_page_id, R.id.info_menu_id, R.id.reload_menu_id};
         assertMenuItemsAreEqual(menu, expectedItems);
@@ -300,16 +301,18 @@
         mAppMenuPropertiesDelegate.prepareMenu(menu, null);
 
         Integer[] expectedItems = {R.id.icon_row_menu_id, R.id.new_tab_menu_id,
-                R.id.new_incognito_tab_menu_id, R.id.all_bookmarks_menu_id,
-                R.id.recent_tabs_menu_id, R.id.open_history_menu_id, R.id.downloads_menu_id,
+                R.id.new_incognito_tab_menu_id, R.id.divider_line_id,
+                R.id.all_bookmarks_row_menu_id, R.id.open_history_menu_id,
+                R.id.downloads_row_menu_id, R.id.recent_tabs_menu_id, R.id.divider_line_id,
                 R.id.translate_id, R.id.share_row_menu_id, R.id.feed_follow_id,
                 R.id.find_in_page_id, R.id.add_to_homescreen_id,
-                R.id.request_desktop_site_row_menu_id, R.id.preferences_id, R.id.help_id};
-        Integer[] expectedTitles = {0, R.string.menu_new_tab, R.string.menu_new_incognito_tab,
-                R.string.menu_bookmarks, R.string.menu_recent_tabs, R.string.menu_history,
-                R.string.menu_downloads, R.string.menu_translate, 0, R.string.menu_follow,
-                R.string.menu_find_in_page, R.string.menu_add_to_homescreen_install, 0,
-                R.string.menu_settings, R.string.menu_help};
+                R.id.request_desktop_site_row_menu_id, R.id.divider_line_id, R.id.preferences_id,
+                R.id.help_id};
+        Integer[] expectedTitles = {0, R.string.menu_new_tab, R.string.menu_new_incognito_tab, 0,
+                R.string.menu_history, 0, 0, R.string.menu_recent_tabs, 0, 0, R.string.menu_follow,
+                R.string.menu_find_in_page, R.string.menu_translate,
+                R.string.menu_add_to_homescreen_install, 0, 0, R.string.menu_settings,
+                R.string.menu_help};
         Integer[] expectedActionBarItems = {R.id.forward_menu_id, R.id.bookmark_this_page_id,
                 R.id.offline_page_id, R.id.info_menu_id, R.id.reload_menu_id};
         assertMenuItemsAreEqual(menu, expectedItems);
@@ -331,12 +334,12 @@
         mAppMenuPropertiesDelegate.prepareMenu(menu, null);
 
         Integer[] expectedItems = {R.id.icon_row_menu_id, R.id.new_tab_menu_id,
-                R.id.new_incognito_tab_menu_id, R.id.all_bookmarks_menu_id,
-                R.id.recent_tabs_menu_id, R.id.open_history_menu_id, R.id.downloads_menu_id,
-                R.id.translate_id, R.id.share_row_menu_id, R.id.feed_follow_id,
-                R.id.find_in_page_id, R.id.add_to_homescreen_id,
-                R.id.request_desktop_site_row_menu_id, R.id.preferences_id, R.id.help_id,
-                R.id.managed_by_menu_id};
+                R.id.new_incognito_tab_menu_id, R.id.divider_line_id, R.id.open_history_menu_id,
+                R.id.downloads_row_menu_id, R.id.all_bookmarks_row_menu_id,
+                R.id.recent_tabs_menu_id, R.id.divider_line_id, R.id.share_row_menu_id,
+                R.id.feed_follow_id, R.id.find_in_page_id, R.id.translate_id,
+                R.id.add_to_homescreen_id, R.id.request_desktop_site_row_menu_id,
+                R.id.divider_line_id, R.id.preferences_id, R.id.help_id, R.id.managed_by_menu_id};
         assertMenuItemsAreEqual(menu, expectedItems);
     }
 
@@ -370,8 +373,7 @@
         mAppMenuPropertiesDelegate.prepareMenu(menu, null);
 
         Integer[] expectedItems = {R.id.update_menu_id, R.id.new_tab_menu_id,
-                R.id.new_incognito_tab_menu_id, R.id.all_bookmarks_menu_id,
-                R.id.recent_tabs_menu_id, R.id.open_history_menu_id, R.id.downloads_menu_id,
+                R.id.new_incognito_tab_menu_id, R.id.recent_tabs_menu_id, R.id.open_history_menu_id,
                 R.id.translate_id, R.id.feed_follow_id, R.id.find_in_page_id,
                 R.id.add_to_homescreen_id, R.id.reader_mode_prefs_id, R.id.preferences_id,
                 R.id.help_id};
@@ -381,7 +383,6 @@
     @Test
     @Config(qualifiers = "sw320dp")
     public void testPageMenuItems_Phone_RegularPage_regroup() {
-        CachedFeatureFlags.setForTesting(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP, true);
         setUpMocksForPageMenu();
         setMenuOptions(false /*isNativePage*/, true /*showTranslate*/, true /*showUpdate*/,
                 true /*showMoveToOtherWindow*/, false /*showReaderModePrefs*/,
@@ -407,63 +408,7 @@
 
     @Test
     @Config(qualifiers = "sw320dp")
-    public void testPageMenuItems_Phone_RegularPage_backward_button_action_bar() {
-        CachedFeatureFlags.setForTesting(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP, true);
-        AppMenuPropertiesDelegateImpl.ACTION_BAR_VARIATION.setForTesting("backward_button");
-        setUpMocksForPageMenu();
-        setMenuOptions(false /*isNativePage*/, true /*showTranslate*/, false /*showUpdate*/,
-                false /*showMoveToOtherWindow*/, false /*showReaderModePrefs*/,
-                true /*showAddToHomeScreen*/, false /*showPaintPreview*/);
-
-        Assert.assertEquals(MenuGroup.PAGE_MENU, mAppMenuPropertiesDelegate.getMenuGroup());
-        Menu menu = createTestMenu();
-        mAppMenuPropertiesDelegate.prepareMenu(menu, null);
-
-        Integer[] expectedItems = {R.id.icon_row_menu_id, R.id.new_tab_menu_id,
-                R.id.new_incognito_tab_menu_id, R.id.divider_line_id, R.id.open_history_menu_id,
-                R.id.downloads_row_menu_id, R.id.all_bookmarks_row_menu_id,
-                R.id.recent_tabs_menu_id, R.id.divider_line_id, R.id.share_row_menu_id,
-                R.id.feed_follow_id, R.id.find_in_page_id, R.id.translate_id,
-                R.id.add_to_homescreen_id, R.id.request_desktop_site_row_menu_id,
-                R.id.divider_line_id, R.id.preferences_id, R.id.info_id, R.id.help_id};
-        Integer[] expectedActionBarItems = {R.id.backward_menu_id, R.id.forward_menu_id,
-                R.id.offline_page_id, R.id.bookmark_this_page_id, R.id.reload_menu_id};
-        assertMenuItemsAreEqual(menu, expectedItems);
-        assertActionBarItemsAreEqual(menu, expectedActionBarItems);
-    }
-
-    @Test
-    @Config(qualifiers = "sw320dp")
-    public void testPageMenuItems_Phone_RegularPage_share_button_action_bar() {
-        CachedFeatureFlags.setForTesting(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP, true);
-        AppMenuPropertiesDelegateImpl.ACTION_BAR_VARIATION.setForTesting("share_button");
-        setUpMocksForPageMenu();
-        setMenuOptions(false /*isNativePage*/, true /*showTranslate*/, false /*showUpdate*/,
-                false /*showMoveToOtherWindow*/, false /*showReaderModePrefs*/,
-                true /*showAddToHomeScreen*/, false /*showPaintPreview*/);
-
-        Assert.assertEquals(MenuGroup.PAGE_MENU, mAppMenuPropertiesDelegate.getMenuGroup());
-        Menu menu = createTestMenu();
-        mAppMenuPropertiesDelegate.prepareMenu(menu, null);
-
-        Integer[] expectedItems = {R.id.icon_row_menu_id, R.id.new_tab_menu_id,
-                R.id.new_incognito_tab_menu_id, R.id.divider_line_id, R.id.open_history_menu_id,
-                R.id.downloads_row_menu_id, R.id.all_bookmarks_row_menu_id,
-                R.id.recent_tabs_menu_id, R.id.divider_line_id, R.id.find_in_page_id,
-                R.id.feed_follow_id, R.id.translate_id, R.id.add_to_homescreen_id,
-                R.id.request_desktop_site_row_menu_id, R.id.divider_line_id, R.id.preferences_id,
-                R.id.info_id, R.id.help_id};
-        Integer[] expectedActionBarItems = {R.id.forward_menu_id, R.id.bookmark_this_page_id,
-                R.id.offline_page_id, R.id.share_menu_button_id, R.id.reload_menu_id};
-        assertMenuItemsAreEqual(menu, expectedItems);
-        assertActionBarItemsAreEqual(menu, expectedActionBarItems);
-    }
-
-    @Test
-    @Config(qualifiers = "sw320dp")
     public void testPageMenuItems_Phone_RegularPage_threebutton_actionbar() {
-        CachedFeatureFlags.setForTesting(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP, true);
-        AppMenuPropertiesDelegateImpl.ACTION_BAR_VARIATION.setForTesting("");
         CachedFeatureFlags.setForTesting(
                 ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR, true);
         AppMenuPropertiesDelegateImpl.THREE_BUTTON_ACTION_BAR_VARIATION.setForTesting(
@@ -493,74 +438,10 @@
 
     @Test
     @Config(qualifiers = "sw320dp")
-    public void testPageMenuItems_Phone_RegularPage_threebutton_actionbar_backward_actionbar() {
-        CachedFeatureFlags.setForTesting(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP, true);
-        CachedFeatureFlags.setForTesting(
-                ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR, true);
-        AppMenuPropertiesDelegateImpl.ACTION_BAR_VARIATION.setForTesting("backward_button");
-        AppMenuPropertiesDelegateImpl.THREE_BUTTON_ACTION_BAR_VARIATION.setForTesting(
-                "action_chip_view");
-        setUpMocksForPageMenu();
-        setMenuOptions(false /*isNativePage*/, true /*showTranslate*/, true /*showUpdate*/,
-                true /*showMoveToOtherWindow*/, false /*showReaderModePrefs*/,
-                true /*showAddToHomeScreen*/, true /*showPaintPreview*/);
-
-        Assert.assertEquals(MenuGroup.PAGE_MENU, mAppMenuPropertiesDelegate.getMenuGroup());
-        Menu menu = createTestMenu();
-        mAppMenuPropertiesDelegate.prepareMenu(menu, null);
-
-        Integer[] expectedItems = {R.id.icon_row_menu_id, R.id.update_menu_id, R.id.new_tab_menu_id,
-                R.id.new_incognito_tab_menu_id, R.id.move_to_other_window_menu_id,
-                R.id.divider_line_id, R.id.open_history_menu_id, R.id.downloads_row_menu_id,
-                R.id.all_bookmarks_row_menu_id, R.id.recent_tabs_menu_id, R.id.divider_line_id,
-                R.id.share_row_menu_id, R.id.feed_follow_id, R.id.paint_preview_show_id,
-                R.id.find_in_page_id, R.id.translate_id, R.id.add_to_homescreen_id,
-                R.id.request_desktop_site_row_menu_id, R.id.divider_line_id, R.id.preferences_id,
-                R.id.info_id, R.id.help_id};
-        Integer[] expectedActionBarItems = {
-                R.id.backward_menu_id, R.id.forward_menu_id, R.id.reload_menu_id};
-        assertMenuItemsAreEqual(menu, expectedItems);
-    }
-
-    @Test
-    @Config(qualifiers = "sw320dp")
-    public void testPageMenuItems_Phone_RegularPage_threebutton_actionbar_share_actionbar() {
-        CachedFeatureFlags.setForTesting(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP, true);
-        CachedFeatureFlags.setForTesting(
-                ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR, true);
-        AppMenuPropertiesDelegateImpl.ACTION_BAR_VARIATION.setForTesting("share_button");
-        AppMenuPropertiesDelegateImpl.THREE_BUTTON_ACTION_BAR_VARIATION.setForTesting(
-                "destination_chip_view");
-        setUpMocksForPageMenu();
-        setMenuOptions(false /*isNativePage*/, true /*showTranslate*/, true /*showUpdate*/,
-                true /*showMoveToOtherWindow*/, false /*showReaderModePrefs*/,
-                true /*showAddToHomeScreen*/, true /*showPaintPreview*/);
-
-        Assert.assertEquals(MenuGroup.PAGE_MENU, mAppMenuPropertiesDelegate.getMenuGroup());
-        Menu menu = createTestMenu();
-        mAppMenuPropertiesDelegate.prepareMenu(menu, null);
-
-        Integer[] expectedItems = {R.id.icon_row_menu_id, R.id.update_menu_id, R.id.new_tab_menu_id,
-                R.id.new_incognito_tab_menu_id, R.id.move_to_other_window_menu_id,
-                R.id.divider_line_id, R.id.open_history_menu_id, R.id.downloads_row_menu_id,
-                R.id.all_bookmarks_row_menu_id, R.id.recent_tabs_menu_id, R.id.divider_line_id,
-                R.id.feed_follow_id, R.id.paint_preview_show_id, R.id.find_in_page_id,
-                R.id.translate_id, R.id.add_to_homescreen_id, R.id.request_desktop_site_row_menu_id,
-                R.id.divider_line_id, R.id.preferences_id, R.id.info_id, R.id.help_id};
-        Integer[] expectedActionBarItems = {
-                R.id.forward_menu_id, R.id.share_menu_button_id, R.id.reload_menu_id};
-        assertMenuItemsAreEqual(menu, expectedItems);
-        assertActionBarItemsAreEqual(menu, expectedActionBarItems);
-    }
-
-    @Test
-    @Config(qualifiers = "sw320dp")
     public void testPageMenuItems_Phone_RegularPage_threebutton_actionbar_add_to_menuitem() {
-        CachedFeatureFlags.setForTesting(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP, true);
         CachedFeatureFlags.setForTesting(
                 ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR, true);
         CachedFeatureFlags.setForTesting(ChromeFeatureList.READ_LATER, true);
-        AppMenuPropertiesDelegateImpl.ACTION_BAR_VARIATION.setForTesting("share_button");
         AppMenuPropertiesDelegateImpl.THREE_BUTTON_ACTION_BAR_VARIATION.setForTesting(
                 "add_to_option");
         setUpMocksForPageMenu();
@@ -577,11 +458,11 @@
                 R.id.divider_line_id, R.id.open_history_menu_id, R.id.downloads_row_menu_id,
                 R.id.all_bookmarks_row_menu_id, R.id.recent_tabs_menu_id,
                 R.id.add_to_divider_line_id, R.id.add_to_menu_id, R.id.divider_line_id,
-                R.id.feed_follow_id, R.id.paint_preview_show_id, R.id.find_in_page_id,
-                R.id.translate_id, R.id.request_desktop_site_row_menu_id, R.id.divider_line_id,
-                R.id.preferences_id, R.id.info_id, R.id.help_id};
+                R.id.share_row_menu_id, R.id.feed_follow_id, R.id.paint_preview_show_id,
+                R.id.find_in_page_id, R.id.translate_id, R.id.request_desktop_site_row_menu_id,
+                R.id.divider_line_id, R.id.preferences_id, R.id.help_id};
         Integer[] expectedActionBarItems = {
-                R.id.forward_menu_id, R.id.share_menu_button_id, R.id.reload_menu_id};
+                R.id.forward_menu_id, R.id.info_menu_id, R.id.reload_menu_id};
         Integer[] expectedAddToItems = {R.id.add_to_bookmarks_menu_id,
                 R.id.add_to_reading_list_menu_id, R.id.add_to_downloads_menu_id,
                 R.id.add_to_homescreen_menu_id};
@@ -596,10 +477,8 @@
     @Config(qualifiers = "sw320dp")
     public void
     testPageMenuItems_Phone_RegularPage_threebutton_actionbar_add_to_menuitem_intall_app() {
-        CachedFeatureFlags.setForTesting(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP, true);
         CachedFeatureFlags.setForTesting(
                 ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR, true);
-        AppMenuPropertiesDelegateImpl.ACTION_BAR_VARIATION.setForTesting("share_button");
         AppMenuPropertiesDelegateImpl.THREE_BUTTON_ACTION_BAR_VARIATION.setForTesting(
                 "add_to_option");
         setUpMocksForPageMenu();
@@ -620,11 +499,12 @@
                 R.id.divider_line_id, R.id.open_history_menu_id, R.id.downloads_row_menu_id,
                 R.id.all_bookmarks_row_menu_id, R.id.recent_tabs_menu_id,
                 R.id.add_to_divider_line_id, R.id.add_to_menu_id, R.id.install_app_id,
-                R.id.divider_line_id, R.id.feed_follow_id, R.id.paint_preview_show_id,
-                R.id.find_in_page_id, R.id.translate_id, R.id.request_desktop_site_row_menu_id,
-                R.id.divider_line_id, R.id.preferences_id, R.id.info_id, R.id.help_id};
+                R.id.divider_line_id, R.id.share_row_menu_id, R.id.feed_follow_id,
+                R.id.paint_preview_show_id, R.id.find_in_page_id, R.id.translate_id,
+                R.id.request_desktop_site_row_menu_id, R.id.divider_line_id, R.id.preferences_id,
+                R.id.help_id};
         Integer[] expectedActionBarItems = {
-                R.id.forward_menu_id, R.id.share_menu_button_id, R.id.reload_menu_id};
+                R.id.forward_menu_id, R.id.info_menu_id, R.id.reload_menu_id};
         Integer[] expectedAddToItems = {
                 R.id.add_to_bookmarks_menu_id, R.id.add_to_downloads_menu_id};
         assertMenuItemsAreEqual(menu, expectedItems);
@@ -701,11 +581,12 @@
         mAppMenuPropertiesDelegate.prepareMenu(menu, null);
 
         Integer[] expectedItems = {R.id.icon_row_menu_id, R.id.new_tab_menu_id,
-                R.id.new_incognito_tab_menu_id, R.id.all_bookmarks_menu_id,
-                R.id.recent_tabs_menu_id, R.id.open_history_menu_id, R.id.downloads_menu_id,
-                R.id.share_row_menu_id, R.id.feed_follow_id, R.id.get_image_descriptions_id,
-                R.id.find_in_page_id, R.id.add_to_homescreen_id,
-                R.id.request_desktop_site_row_menu_id, R.id.preferences_id, R.id.help_id};
+                R.id.new_incognito_tab_menu_id, R.id.divider_line_id, R.id.open_history_menu_id,
+                R.id.all_bookmarks_row_menu_id, R.id.downloads_row_menu_id,
+                R.id.recent_tabs_menu_id, R.id.divider_line_id, R.id.share_row_menu_id,
+                R.id.feed_follow_id, R.id.get_image_descriptions_id, R.id.find_in_page_id,
+                R.id.add_to_homescreen_id, R.id.request_desktop_site_row_menu_id,
+                R.id.divider_line_id, R.id.preferences_id, R.id.help_id};
 
         assertMenuItemsAreEqual(menu, expectedItems);
 
@@ -864,10 +745,6 @@
                 AppMenuSimilarSelectionType.NO_MATCH,
                 mAppMenuPropertiesDelegate.findSimilarSelectionPattern(
                         R.id.downloads_menu_id, R.id.all_bookmarks_menu_id));
-        Assert.assertEquals("Should no match for all bookmarks then share",
-                AppMenuSimilarSelectionType.NO_MATCH,
-                mAppMenuPropertiesDelegate.findSimilarSelectionPattern(
-                        R.id.all_bookmarks_menu_id, R.id.share_menu_button_id));
         Assert.assertEquals("Should no match for new tab then find in page",
                 AppMenuSimilarSelectionType.NO_MATCH,
                 mAppMenuPropertiesDelegate.findSimilarSelectionPattern(
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
index df7c143..c822bf9 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
@@ -177,12 +177,12 @@
         mTabbedAppMenuPropertiesDelegate.prepareMenu(menu, null);
 
         Integer[] expectedItems = {R.id.icon_row_menu_id, R.id.new_tab_menu_id,
-                R.id.new_incognito_tab_menu_id, R.id.all_bookmarks_menu_id,
-                R.id.recent_tabs_menu_id, R.id.open_history_menu_id, R.id.downloads_menu_id,
-                R.id.translate_id, R.id.share_row_menu_id, R.id.feed_follow_id,
-                R.id.find_in_page_id, R.id.add_to_homescreen_id,
-                R.id.request_desktop_site_row_menu_id, R.id.preferences_id, R.id.help_id,
-                R.id.managed_by_menu_id};
+                R.id.new_incognito_tab_menu_id, R.id.divider_line_id, R.id.open_history_menu_id,
+                R.id.downloads_row_menu_id, R.id.all_bookmarks_row_menu_id,
+                R.id.recent_tabs_menu_id, R.id.divider_line_id, R.id.translate_id,
+                R.id.share_row_menu_id, R.id.feed_follow_id, R.id.find_in_page_id,
+                R.id.add_to_homescreen_id, R.id.request_desktop_site_row_menu_id,
+                R.id.divider_line_id, R.id.preferences_id, R.id.help_id, R.id.managed_by_menu_id};
         assertMenuItemsAreEqual(menu, expectedItems);
     }
 
diff --git a/chrome/android/monochrome/scripts/monochrome_python_tests.pydeps b/chrome/android/monochrome/scripts/monochrome_python_tests.pydeps
index 8a0cd5a..93ca9c0 100644
--- a/chrome/android/monochrome/scripts/monochrome_python_tests.pydeps
+++ b/chrome/android/monochrome/scripts/monochrome_python_tests.pydeps
@@ -44,7 +44,6 @@
 //third_party/catapult/devil/devil/utils/reraiser_thread.py
 //third_party/catapult/devil/devil/utils/timeout_retry.py
 //third_party/catapult/devil/devil/utils/watchdog_timer.py
-//third_party/catapult/third_party/six/six.py
 //third_party/catapult/third_party/typ/typ/__init__.py
 //third_party/catapult/third_party/typ/typ/arg_parser.py
 //third_party/catapult/third_party/typ/typ/artifacts.py
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index 19a8c13..2df7e6c 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -70,7 +70,7 @@
   output_dir = "$root_gen_dir/chrome"
   outputs =
       [ "grit/generated_resources.h" ] +
-      process_file_template(locales_with_fake_bidi,
+      process_file_template(locales_with_pseudolocales,
                             [ "generated_resources_{{source_name_part}}.pak" ])
   if (is_android) {
     outputs += android_generated_java_resources
@@ -92,7 +92,7 @@
   output_dir = "$root_gen_dir/chrome"
   outputs = [ "grit/google_chrome_strings.h" ] +
             process_file_template(
-                locales_with_fake_bidi,
+                locales_with_pseudolocales,
                 [ "google_chrome_strings_{{source_name_part}}.pak" ])
 }
 
@@ -102,7 +102,7 @@
   output_dir = "$root_gen_dir/chrome"
   outputs =
       [ "grit/chromium_strings.h" ] +
-      process_file_template(locales_with_fake_bidi,
+      process_file_template(locales_with_pseudolocales,
                             [ "chromium_strings_{{source_name_part}}.pak" ])
 }
 
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index bcca8fe..716c7eff8 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -778,15 +778,6 @@
         <message name="IDS_SIGNIN_ERROR_SECONDARY_ACCOUNT_DISPLAY_SOURCE" desc="Context title shown in the notification header of sign-in error notification for Chromium OS Secondary Accounts.">
           Chromium OS System
         </message>
-        <message name="IDS_SYNC_NEEDS_KEYS_FOR_EVERYTHING_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sync error notification when the user needs to reauth to retrieve the sync encryption keys and resume sync.">
-          Chromium OS could not sync your data.
-        </message>
-        <message name="IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sync error notification when the user needs to reauth to retrieve the sync encryption keys, required to resume syncing of passwords specifically (other sync data works normally).">
-          Chromium OS could not sync your passwords.
-        </message>
-        <message name="IDS_SYNC_PASSPHRASE_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sync error notification when the user needs to update their sync passphrase.">
-          Chromium OS could not sync your data. Please update your Sync passphrase.
-        </message>
         <message name="IDS_SYNC_SIGN_IN_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sign-in error notification when the user's sign-in credentials are out of date.">
           Chromium OS could not sync your data because your account sign-in details are out of date.
         </message>
diff --git a/chrome/app/chromium_strings_grd/IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1 b/chrome/app/chromium_strings_grd/IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1
deleted file mode 100644
index 6a44b8c..0000000
--- a/chrome/app/chromium_strings_grd/IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-839267c25e58108f3a41cf49e06c38bd3dd5ffc1
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 7c147a68..6d818753 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6031,6 +6031,9 @@
       <message name="IDS_READING_LIST_DISCOVERY_PROMO" desc="Text shown on promotional UI appearing next to the reading list button">
         When you're ready, find your reading list here
       </message>
+      <message name="IDS_READING_LIST_ENTRY_POINT_PROMO" desc="Text shown on promotional UI to encourage users to add a tab to their reading list">
+        Add this page to reading list
+      </message>
       <message name="IDS_REOPEN_TAB_PROMO" desc="Text shown on promotional UI appearing next to the app menu button">
         Reopen a tab if you accidentally closed it
       </message>
@@ -8355,7 +8358,7 @@
         Sign-in error
       </message>
       <message name="IDS_SYNC_ERROR_BUBBLE_VIEW_TITLE" desc="Title in the sync error bubble view/notification.">
-        Sync error
+        Sync isn't working
       </message>
       <message name="IDS_SYNC_ERROR_USER_MENU_TITLE" desc="Title of the sync/signin error header of desktop user menu.">
         Sync isn't working
@@ -8376,10 +8379,10 @@
         Enter passphrase
       </message>
       <message name="IDS_SYNC_ERROR_USER_MENU_RECOVERABILITY_BUTTON" desc="Button in the header of desktop user menu that prompts the user to reauthentication to fix the sync error affecting encrypted datatypes.">
-        Fix now
+        Verify it's you
       </message>
       <message name="IDS_SYNC_ERROR_USER_MENU_RETRIEVE_KEYS_BUTTON" desc="Button in the header of desktop user menu that prompts the user to reauthentication to fix the sync error affecting encrypted datatypes.">
-        Fix now
+        Verify it's you
       </message>
       <message name="IDS_SYNC_ERROR_USER_MENU_SIGNIN_AGAIN_BUTTON" desc="Button in the header of desktop user menu that signs out and signs back in in an attempt to resolve an unrecoverable error.">
         Sign in again
@@ -8391,6 +8394,18 @@
         Open settings
       </message>
 
+      <if expr="chromeos">
+        <message name="IDS_SYNC_PASSPHRASE_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sync error bubble view when the user needs to enter their sync passphrase.">
+          To start sync, enter your passphrase
+        </message>
+        <message name="IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sync error notification when the user needs to reauth to retrieve the sync encryption keys, required to resume syncing of passwords specifically (other sync data works normally).">
+          To sync your passwords, verify it's you
+        </message>
+        <message name="IDS_SYNC_NEEDS_KEYS_FOR_EVERYTHING_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sync error notification when the user needs to reauth to retrieve the sync encryption keys and resume sync.">
+          To start sync, verify it's you
+        </message>
+      </if>
+
       <if expr="not use_titlecase">
         <message name="IDS_SYNC_UNAVAILABLE_ERROR_BUBBLE_VIEW_ACCEPT" desc="The accept button in the sync error bubble view when sync is not available for their domain.">
           OK...
@@ -8460,7 +8475,7 @@
           Enter passphrase
         </message>
         <message name="IDS_SYNC_STATUS_NEEDS_KEYS_BUTTON" desc="Button indicating user must reauthenticate to sync encrypted datatypes.">
-          Fix now
+          Verify it's you
         </message>
 
         <message name="IDS_SYNC_SERVER_IS_UNREACHABLE" desc="The message to display in the New Tab Page and Settings Page sync section when the server is unreachable.">
@@ -11183,6 +11198,9 @@
         Send feedback
     </message>
     </if>
+    <message name="IDS_CHROMELABS_SEND_FEEDBACK_DESCRIPTION_PLACEHOLDER" desc="The placeholder text for the send feedback dialog to submit feedback for each experiment.">
+      Send feedback for <ph name="EXPERIMENT_NAME">$1<ex>Tab Scrolling</ex></ph>.
+    </message>
   </messages>
 </release>
 </grit>
diff --git a/chrome/app/generated_resources_grd/IDS_CHROMELABS_SEND_FEEDBACK_DESCRIPTION_PLACEHOLDER.png.sha1 b/chrome/app/generated_resources_grd/IDS_CHROMELABS_SEND_FEEDBACK_DESCRIPTION_PLACEHOLDER.png.sha1
new file mode 100644
index 0000000..635e06d
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_CHROMELABS_SEND_FEEDBACK_DESCRIPTION_PLACEHOLDER.png.sha1
@@ -0,0 +1 @@
+321c74a3b14b7f98180a0a1f17cf4b1cde423bc6
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_READING_LIST_ENTRY_POINT_PROMO.png.sha1 b/chrome/app/generated_resources_grd/IDS_READING_LIST_ENTRY_POINT_PROMO.png.sha1
new file mode 100644
index 0000000..0ccba30
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_READING_LIST_ENTRY_POINT_PROMO.png.sha1
@@ -0,0 +1 @@
+360426ccb3305f41f4c1966e34f16a737c605abc
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_SYNC_ERROR_BUBBLE_VIEW_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_SYNC_ERROR_BUBBLE_VIEW_TITLE.png.sha1
new file mode 100644
index 0000000..892641eb
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_SYNC_ERROR_BUBBLE_VIEW_TITLE.png.sha1
@@ -0,0 +1 @@
+d6a8db1cd7e0fbbd03ef8fb73e5b4fbda312d082
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_SYNC_ERROR_USER_MENU_RECOVERABILITY_BUTTON.png.sha1 b/chrome/app/generated_resources_grd/IDS_SYNC_ERROR_USER_MENU_RECOVERABILITY_BUTTON.png.sha1
index 6dbd2f47..9514c29 100644
--- a/chrome/app/generated_resources_grd/IDS_SYNC_ERROR_USER_MENU_RECOVERABILITY_BUTTON.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_SYNC_ERROR_USER_MENU_RECOVERABILITY_BUTTON.png.sha1
@@ -1 +1 @@
-a52e087aa4b3cb0c688d973c4df00af1652c968f
\ No newline at end of file
+c8075aba166a6a956ee17ec27c6b3d90d5c0db1c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_SYNC_ERROR_USER_MENU_RETRIEVE_KEYS_BUTTON.png.sha1 b/chrome/app/generated_resources_grd/IDS_SYNC_ERROR_USER_MENU_RETRIEVE_KEYS_BUTTON.png.sha1
index 63f55821..acb568a8d9 100644
--- a/chrome/app/generated_resources_grd/IDS_SYNC_ERROR_USER_MENU_RETRIEVE_KEYS_BUTTON.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_SYNC_ERROR_USER_MENU_RETRIEVE_KEYS_BUTTON.png.sha1
@@ -1 +1 @@
-0a129f2b186ca99e1df0f56197f6846290692368
\ No newline at end of file
+fb0825aa1b447ad5da8a5895d8feed44faf0e14e
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_SYNC_NEEDS_KEYS_FOR_EVERYTHING_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_SYNC_NEEDS_KEYS_FOR_EVERYTHING_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1
new file mode 100644
index 0000000..04b1a91
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_SYNC_NEEDS_KEYS_FOR_EVERYTHING_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1
@@ -0,0 +1 @@
+e7abdfc0e11e7cc90c649eea3c2154a22c63be36
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1
new file mode 100644
index 0000000..4a24234
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1
@@ -0,0 +1 @@
+d6a8db1cd7e0fbbd03ef8fb73e5b4fbda312d082
diff --git a/chrome/app/generated_resources_grd/IDS_SYNC_PASSPHRASE_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_SYNC_PASSPHRASE_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1
new file mode 100644
index 0000000..d94f102
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_SYNC_PASSPHRASE_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1
@@ -0,0 +1 @@
+44d16bfc406fbbf701c30b9fd82fd250f4a9c251
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_SYNC_STATUS_NEEDS_KEYS_BUTTON.png.sha1 b/chrome/app/generated_resources_grd/IDS_SYNC_STATUS_NEEDS_KEYS_BUTTON.png.sha1
index d9c3efd..539500a 100644
--- a/chrome/app/generated_resources_grd/IDS_SYNC_STATUS_NEEDS_KEYS_BUTTON.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_SYNC_STATUS_NEEDS_KEYS_BUTTON.png.sha1
@@ -1 +1 @@
-61dae1010b38fbcdc4d6f946bb342894f7cb8425
\ No newline at end of file
+30c0cc0ba806dbab50bbb811c60b7e1e60fe8d26
\ No newline at end of file
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index e7234e2e5..1ae19e8 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -784,15 +784,6 @@
         <message name="IDS_SIGNIN_ERROR_SECONDARY_ACCOUNT_DISPLAY_SOURCE" desc="Context title shown in the notification header of sign-in error notification for Chrome OS Secondary Accounts.">
           Chrome OS System
         </message>
-        <message name="IDS_SYNC_NEEDS_KEYS_FOR_EVERYTHING_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sync error notification when the user needs to reauth to retrieve the sync encryption keys and resume sync.">
-          Chrome OS could not sync your data.
-        </message>
-        <message name="IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sync error notification when the user needs to reauth to retrieve the sync encryption keys, required to resume syncing of passwords specifically (other sync data works normally).">
-          Chrome OS could not sync your passwords.
-        </message>
-        <message name="IDS_SYNC_PASSPHRASE_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sync error bubble view when the user needs to update their sync passphrase.">
-          Chrome OS could not sync your data. Please update your Sync passphrase.
-        </message>
         <message name="IDS_SYNC_SIGN_IN_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sign-in error notification when the user's sign-in credentials are out of date.">
           Chrome OS could not sync your data because your account sign-in details are out of date.
         </message>
diff --git a/chrome/app/google_chrome_strings_grd/IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1
deleted file mode 100644
index 6a44b8c..0000000
--- a/chrome/app/google_chrome_strings_grd/IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-839267c25e58108f3a41cf49e06c38bd3dd5ffc1
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 6123818..9fc9751 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -2682,7 +2682,9 @@
     Change PIN
   </message>
   <message name="IDS_SETTINGS_PEOPLE_LOCK_SCREEN_NUM_FINGERPRINTS" desc="Text on the lock screen which tells users how many fingerprints they have enrolled.">
-    <ph name="NUM_FINGERPRINTS">$1<ex>1</ex></ph> fingerprints set up
+    {COUNT, plural,
+      =1 {1 fingerprint set up}
+      other {{COUNT} fingerprints set up}}
   </message>
   <message name="IDS_SETTINGS_PEOPLE_LOCK_SCREEN_NONE" desc="Text on the people page which notifies the user that the device will not show lock screen or prompt for auth after waking from sleep.">
     Sign in automatically
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PEOPLE_LOCK_SCREEN_NUM_FINGERPRINTS.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PEOPLE_LOCK_SCREEN_NUM_FINGERPRINTS.png.sha1
new file mode 100644
index 0000000..0593faec
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_PEOPLE_LOCK_SCREEN_NUM_FINGERPRINTS.png.sha1
@@ -0,0 +1 @@
+7aa82dcaed6f294e699ecc3a1dc751978d6a6508
\ No newline at end of file
diff --git a/chrome/app/resources/BUILD.gn b/chrome/app/resources/BUILD.gn
index 24db1ea9..c986cc85 100644
--- a/chrome/app/resources/BUILD.gn
+++ b/chrome/app/resources/BUILD.gn
@@ -16,7 +16,7 @@
   output_dir = "$root_gen_dir/chrome"
   outputs =
       [ "grit/locale_settings.h" ] +
-      process_file_template(locales_with_fake_bidi,
+      process_file_template(locales_with_pseudolocales,
                             [ "locale_settings_{{source_name_part}}.pak" ])
 }
 
@@ -39,7 +39,7 @@
   defines = chrome_grit_defines
   outputs = [ "grit/platform_locale_settings.h" ] +
             process_file_template(
-                locales_with_fake_bidi,
+                locales_with_pseudolocales,
                 [ "platform_locale_settings_{{source_name_part}}.pak" ])
   output_dir = "$root_gen_dir/chrome"
 }
diff --git a/chrome/app/shared_settings_strings.grdp b/chrome/app/shared_settings_strings.grdp
index 6fca79f..c8f25b4 100644
--- a/chrome/app/shared_settings_strings.grdp
+++ b/chrome/app/shared_settings_strings.grdp
@@ -233,7 +233,7 @@
     Sync isn't working
   </message>
   <message name="IDS_SETTINGS_PEOPLE_SYNC_PASSWORDS_NOT_WORKING" desc="The label informing the user that password sync is currently not working.">
-    Error syncing passwords
+    Password sync isn't working
   </message>
   <message name="IDS_SETTINGS_SYNC_ADVANCED_PAGE_TITLE" desc="Name of the settings page which manages data used by sync.">
     Manage sync
@@ -254,7 +254,7 @@
   </message>
   <!-- Sync Control Subpage (strings used by the <settings-sync-controls> element) -->
   <message name="IDS_SETTINGS_AUTOFILL_CHECKBOX_LABEL" desc="Label for the checkbox which enables or disables syncing autofill settings between multiple browser instances.">
-    Addresses, phone numbers, and more
+    Addresses and more
   </message>
   <message name="IDS_SETTINGS_HISTORY_CHECKBOX_LABEL" desc="Label for the checkbox which enables or disables syncing history between multiple browser instances.">
     History
@@ -263,7 +263,7 @@
     Extensions
   </message>
   <message name="IDS_SETTINGS_OPEN_TABS_CHECKBOX_LABEL" desc="Label for the checkbox which enables or disables syncing open tabs between multiple browser instances.">
-    Open Tabs
+    Open tabs
   </message>
   <message name="IDS_SETTINGS_WIFI_CONFIGURATIONS_CHECKBOX_LABEL" desc="Label for the checkbox which enables syncing of Wi-Fi networks across devices.">
     Wi-Fi networks
@@ -298,13 +298,13 @@
     Submit
   </message>
   <message name="IDS_SETTINGS_ENCRYPT_WITH_GOOGLE_CREDENTIALS_LABEL" desc="Label for the radio button which, when selected, causes synced settings to be encrypted with the Google login for the current browser profile.">
-    Encrypt synced passwords with your Google username and password
+    Encrypt synced passwords with your Google Account
   </message>
   <message name="IDS_SETTINGS_BOOKMARKS_CHECKBOX_LABEL" desc="Label for the checkbox which enables or disables syncing bookmarks between multiple browser instances.">
     Bookmarks
   </message>
   <message name="IDS_SETTINGS_READING_LIST_CHECKBOX_LABEL" desc="Label for the checkbox which enables or disables syncing reading list between multiple browser instances.">
-    Reading List
+    Reading list
   </message>
   <message name="IDS_SETTINGS_ENCRYPTION_OPTIONS" desc="Title for the section which includes options for encrypting sync settings.">
     Encryption options
diff --git a/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_AUTOFILL_CHECKBOX_LABEL.png.sha1 b/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_AUTOFILL_CHECKBOX_LABEL.png.sha1
new file mode 100644
index 0000000..8c88afe
--- /dev/null
+++ b/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_AUTOFILL_CHECKBOX_LABEL.png.sha1
@@ -0,0 +1 @@
+3c6ddcd83b32bc2c07faf29b2716aac96a9a6a3b
\ No newline at end of file
diff --git a/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_ENCRYPT_WITH_GOOGLE_CREDENTIALS_LABEL.png.sha1 b/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_ENCRYPT_WITH_GOOGLE_CREDENTIALS_LABEL.png.sha1
new file mode 100644
index 0000000..acb5aec
--- /dev/null
+++ b/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_ENCRYPT_WITH_GOOGLE_CREDENTIALS_LABEL.png.sha1
@@ -0,0 +1 @@
+8fc0a1228d9713605b9e5f694db56ee1f88409ca
\ No newline at end of file
diff --git a/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_OPEN_TABS_CHECKBOX_LABEL.png.sha1 b/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_OPEN_TABS_CHECKBOX_LABEL.png.sha1
new file mode 100644
index 0000000..8c88afe
--- /dev/null
+++ b/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_OPEN_TABS_CHECKBOX_LABEL.png.sha1
@@ -0,0 +1 @@
+3c6ddcd83b32bc2c07faf29b2716aac96a9a6a3b
\ No newline at end of file
diff --git a/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_PEOPLE_SYNC_PASSWORDS_NOT_WORKING.png.sha1 b/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_PEOPLE_SYNC_PASSWORDS_NOT_WORKING.png.sha1
new file mode 100644
index 0000000..539500a
--- /dev/null
+++ b/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_PEOPLE_SYNC_PASSWORDS_NOT_WORKING.png.sha1
@@ -0,0 +1 @@
+30c0cc0ba806dbab50bbb811c60b7e1e60fe8d26
\ No newline at end of file
diff --git a/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_READING_LIST_CHECKBOX_LABEL.png.sha1 b/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_READING_LIST_CHECKBOX_LABEL.png.sha1
index 91460b9..8c88afe 100644
--- a/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_READING_LIST_CHECKBOX_LABEL.png.sha1
+++ b/chrome/app/shared_settings_strings_grdp/IDS_SETTINGS_READING_LIST_CHECKBOX_LABEL.png.sha1
@@ -1 +1 @@
-d82a9aba092da44ee87f4da5c3e60788ca8ce0d1
\ No newline at end of file
+3c6ddcd83b32bc2c07faf29b2716aac96a9a6a3b
\ No newline at end of file
diff --git a/chrome/app_shim/app_shim_controller.mm b/chrome/app_shim/app_shim_controller.mm
index ec0f44a9..7237f74 100644
--- a/chrome/app_shim/app_shim_controller.mm
+++ b/chrome/app_shim/app_shim_controller.mm
@@ -17,6 +17,7 @@
 #include "base/mac/bundle_locations.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/launch_services_util.h"
+#include "base/mac/mac_util.h"
 #include "base/mac/mach_logging.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
@@ -337,6 +338,17 @@
           : chrome::mojom::AppShimLaunchType::kNormal;
   app_shim_info->files = launch_files_;
 
+  if (base::mac::WasLaunchedAsHiddenLoginItem()) {
+    app_shim_info->login_item_restore_state =
+        chrome::mojom::AppShimLoginItemRestoreState::kHidden;
+  } else if (base::mac::WasLaunchedAsLoginOrResumeItem()) {
+    app_shim_info->login_item_restore_state =
+        chrome::mojom::AppShimLoginItemRestoreState::kWindowed;
+  } else {
+    app_shim_info->login_item_restore_state =
+        chrome::mojom::AppShimLoginItemRestoreState::kNone;
+  }
+
   host_bootstrap_->OnShimConnected(
       std::move(host_receiver_), std::move(app_shim_info),
       base::BindOnce(&AppShimController::OnShimConnectedResponse,
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index e800b5d6..b20e05ec 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -572,6 +572,8 @@
     "history/top_sites_factory.h",
     "history/web_history_service_factory.cc",
     "history/web_history_service_factory.h",
+    "history_clusters/history_clusters_tab_helper.cc",
+    "history_clusters/history_clusters_tab_helper.h",
     "icon_loader.cc",
     "icon_loader.h",
     "icon_manager.cc",
@@ -964,14 +966,8 @@
     "optimization_guide/prediction/prediction_model_download_observer.h",
     "page_load_metrics/observers/aborts_page_load_metrics_observer.cc",
     "page_load_metrics/observers/aborts_page_load_metrics_observer.h",
-    "page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc",
-    "page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h",
     "page_load_metrics/observers/ad_metrics/floc_page_load_metrics_observer.cc",
     "page_load_metrics/observers/ad_metrics/floc_page_load_metrics_observer.h",
-    "page_load_metrics/observers/ad_metrics/frame_data.cc",
-    "page_load_metrics/observers/ad_metrics/frame_data.h",
-    "page_load_metrics/observers/ad_metrics/page_ad_density_tracker.cc",
-    "page_load_metrics/observers/ad_metrics/page_ad_density_tracker.h",
     "page_load_metrics/observers/core/amp_page_load_metrics_observer.cc",
     "page_load_metrics/observers/core/amp_page_load_metrics_observer.h",
     "page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc",
@@ -2112,6 +2108,7 @@
     "//components/optimization_guide/core",
     "//components/os_crypt",
     "//components/page_load_metrics/browser",
+    "//components/page_load_metrics/browser/observers/ad_metrics",
     "//components/page_load_metrics/common",
     "//components/paint_preview/buildflags",
     "//components/paint_preview/features",
@@ -4117,6 +4114,7 @@
       "themes/theme_service.h",
       "themes/theme_service_factory.cc",
       "themes/theme_service_factory.h",
+      "themes/theme_service_observer.h",
       "themes/theme_syncable_service.cc",
       "themes/theme_syncable_service.h",
       "upgrade_detector/build_state.cc",
@@ -5009,6 +5007,8 @@
       "notifications/notification_alert_service_bridge.mm",
       "notifications/notification_platform_bridge_mac.h",
       "notifications/notification_platform_bridge_mac.mm",
+      "notifications/notification_platform_bridge_mac_metrics.cc",
+      "notifications/notification_platform_bridge_mac_metrics.h",
       "notifications/notification_platform_bridge_mac_unnotification.h",
       "notifications/notification_platform_bridge_mac_unnotification.mm",
       "notifications/notification_platform_bridge_mac_utils.h",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 0b11f9b4f..3686857b 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -122,7 +122,6 @@
 #include "components/security_state/core/security_state.h"
 #include "components/send_tab_to_self/features.h"
 #include "components/services/heap_profiling/public/cpp/switches.h"
-#include "components/shared_highlighting/core/common/features.h"
 #include "components/shared_highlighting/core/common/shared_highlighting_features.h"
 #include "components/signin/core/browser/dice_account_reconcilor_delegate.h"
 #include "components/signin/public/base/signin_buildflags.h"
@@ -1506,6 +1505,22 @@
     {" - tabs do not shrink", kMinimumTabWidthSettingFull,
      base::size(kMinimumTabWidthSettingFull), nullptr}};
 
+const FeatureEntry::FeatureParam kTabHoverCardImagesOptimizationCaptureSpeed[] =
+    {{features::kTabHoverCardImagesNotReadyDelayParameterName, "0"},
+     {features::kTabHoverCardImagesLoadingDelayParameterName, "0"},
+     {features::kTabHoverCardImagesLoadedDelayParameterName, "0"}};
+const FeatureEntry::FeatureParam
+    kTabHoverCardImagesOptimizationResourceUsage[] = {
+        {features::kTabHoverCardImagesNotReadyDelayParameterName, "800"},
+        {features::kTabHoverCardImagesLoadingDelayParameterName, "300"},
+        {features::kTabHoverCardImagesLoadedDelayParameterName, "300"}};
+
+const FeatureEntry::FeatureVariation kTabHoverCardImagesVariations[] = {
+    {" capture speed", kTabHoverCardImagesOptimizationCaptureSpeed,
+     base::size(kTabHoverCardImagesOptimizationCaptureSpeed), nullptr},
+    {" resource usage", kTabHoverCardImagesOptimizationResourceUsage,
+     base::size(kTabHoverCardImagesOptimizationResourceUsage), nullptr}};
+
 const FeatureEntry::FeatureParam kPromoBrowserCommandUnknownCommandParam[] = {
     {features::kPromoBrowserCommandIdParam, "0"}};
 const FeatureEntry::FeatureParam
@@ -2024,15 +2039,6 @@
          base::size(kPhotoPickerVideoSupportEnabledWithAnimatedThumbnails),
          nullptr}};
 
-const FeatureEntry::FeatureParam kTabbedAppOverflowMenuRegroupBackward[] = {
-    {"action_bar", "backward_button"}};
-const FeatureEntry::FeatureParam kTabbedAppOverflowMenuRegroupShare[] = {
-    {"action_bar", "share_button"}};
-const FeatureEntry::FeatureVariation kTabbedAppOverflowMenuRegroupVariations[] =
-    {{"(backward button)", kTabbedAppOverflowMenuRegroupBackward,
-      base::size(kTabbedAppOverflowMenuRegroupBackward), nullptr},
-     {"(share button)", kTabbedAppOverflowMenuRegroupShare,
-      base::size(kTabbedAppOverflowMenuRegroupShare), nullptr}};
 const FeatureEntry::FeatureParam
     kTabbedAppOverflowMenuThreeButtonActionbarAction[] = {
         {"three_button_action_bar", "action_chip_view"}};
@@ -3092,7 +3098,7 @@
      FEATURE_VALUE_TYPE(media::kDeprecateLowUsageCodecs)},
 #endif  // defined(OS_CHROMEOS)
 
-#if defined(OS_LINUX) && !defined(OS_ANDROID)
+#if defined(OS_LINUX)
     {
         "enable-accelerated-video-decode",
         flag_descriptions::kAcceleratedVideoDecodeName,
@@ -3110,7 +3116,7 @@
         kOsMac | kOsWin | kOsCrOS | kOsAndroid | kOsLinux,
         SINGLE_DISABLE_VALUE_TYPE(switches::kDisableAcceleratedVideoDecode),
     },
-#endif  // defined(OS_LINUX) && !defined(OS_ANDROID)
+#endif  // defined(OS_LINUX)
     {
         "disable-accelerated-video-encode",
         flag_descriptions::kAcceleratedVideoEncodeName,
@@ -3361,10 +3367,10 @@
      flag_descriptions::kExtensionContentVerificationName,
      flag_descriptions::kExtensionContentVerificationDescription, kOsDesktop,
      MULTI_VALUE_TYPE(kExtensionContentVerificationChoices)},
-    {"preemptive-link-to-text-generation",
-     flag_descriptions::kPreemptiveLinkToTextGenerationName,
-     flag_descriptions::kPreemptiveLinkToTextGenerationDescription, kOsAll,
-     FEATURE_VALUE_TYPE(features::kPreemptiveLinkToTextGeneration)},
+    {"preemtive-link-to-text-generation",
+     flag_descriptions::kPreemtiveLinkToTextGenerationName,
+     flag_descriptions::kPreemtiveLinkToTextGenerationDescription, kOsAll,
+     FEATURE_VALUE_TYPE(features::kPreemtiveLinkToTextGeneration)},
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     {"keyboard-based-display-arrangement-in-settings",
      flag_descriptions::kKeyboardBasedDisplayArrangementInSettingsName,
@@ -4423,17 +4429,6 @@
 #endif  // OS_ANDROID
 
 #if defined(OS_ANDROID)
-    {"tabbed-app-overflow-menu-icons",
-     flag_descriptions::kTabbedAppOverflowMenuIconsName,
-     flag_descriptions::kTabbedAppOverflowMenuIconsDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kTabbedAppOverflowMenuIcons)},
-    {"tabbed-app-overflow-menu-regroup",
-     flag_descriptions::kTabbedAppOverflowMenuRegroupName,
-     flag_descriptions::kTabbedAppOverflowMenuRegroupDescription, kOsAndroid,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         chrome::android::kTabbedAppOverflowMenuRegroup,
-         kTabbedAppOverflowMenuRegroupVariations,
-         "AndroidAppMenuUiRework")},
     {"tabbed-app-overflow-menu-three-button-actionbar",
      flag_descriptions::kTabbedAppOverflowMenuThreeButtonActionbarName,
      flag_descriptions::kTabbedAppOverflowMenuThreeButtonActionbarDescription,
@@ -4849,7 +4844,9 @@
 
     {"tab-hover-card-images", flag_descriptions::kTabHoverCardImagesName,
      flag_descriptions::kTabHoverCardImagesDescription, kOsDesktop,
-     FEATURE_VALUE_TYPE(features::kTabHoverCardImages)},
+     FEATURE_WITH_PARAMS_VALUE_TYPE(features::kTabHoverCardImages,
+                                    kTabHoverCardImagesVariations,
+                                    "TabHoverCardImages")},
 
     {"stop-in-background", flag_descriptions::kStopInBackgroundName,
      flag_descriptions::kStopInBackgroundDescription, kOsAndroid,
@@ -6317,6 +6314,9 @@
     {"os-settings-deep-linking", flag_descriptions::kOsSettingsDeepLinkingName,
      flag_descriptions::kOsSettingsDeepLinkingDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kOsSettingsDeepLinking)},
+    {"help-app-launcher-search", flag_descriptions::kHelpAppLauncherSearchName,
+     flag_descriptions::kHelpAppLauncherSearchDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(chromeos::features::kHelpAppLauncherSearch)},
     {"help-app-search-service-integration",
      flag_descriptions::kHelpAppSearchServiceIntegrationName,
      flag_descriptions::kHelpAppSearchServiceIntegrationDescription, kOsCrOS,
diff --git a/chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.cc b/chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.cc
index 35fe338..348c81d4 100644
--- a/chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.cc
+++ b/chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.cc
@@ -114,6 +114,11 @@
   return app_shim_info_->files;
 }
 
+chrome::mojom::AppShimLoginItemRestoreState
+AppShimHostBootstrap::GetLoginItemRestoreState() const {
+  return app_shim_info_->login_item_restore_state;
+}
+
 bool AppShimHostBootstrap::IsMultiProfile() const {
   // PWAs and bookmark apps are multi-profile capable.
   return app_shim_info_->app_url.is_valid();
diff --git a/chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.h b/chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.h
index 1e6bd6d..dd3f744 100644
--- a/chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.h
+++ b/chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.h
@@ -68,6 +68,9 @@
   // onto the app bundle or dock icon.
   const std::vector<base::FilePath>& GetLaunchFiles() const;
 
+  // Indicates if the app launched during OS login.
+  chrome::mojom::AppShimLoginItemRestoreState GetLoginItemRestoreState() const;
+
   // Returns true if this app supports multiple profiles. If so, it will not be
   // required that GetProfilePath be a valid profile path.
   bool IsMultiProfile() const;
diff --git a/chrome/browser/apps/app_shim/app_shim_manager_mac.cc b/chrome/browser/apps/app_shim/app_shim_manager_mac.cc
index 3b464a8..72a7cd95 100644
--- a/chrome/browser/apps/app_shim/app_shim_manager_mac.cc
+++ b/chrome/browser/apps/app_shim/app_shim_manager_mac.cc
@@ -329,11 +329,13 @@
       const base::FilePath profile_path = bootstrap->GetProfilePath();
       const std::vector<base::FilePath> launch_files =
           bootstrap->GetLaunchFiles();
+      const chrome::mojom::AppShimLoginItemRestoreState
+          login_item_restore_state = bootstrap->GetLoginItemRestoreState();
       LoadAndLaunchAppCallback launch_callback = base::BindOnce(
           &AppShimManager::OnShimProcessConnectedAndAllLaunchesDone,
           weak_factory_.GetWeakPtr(), std::move(bootstrap));
       LoadAndLaunchApp(app_id, profile_path, launch_files,
-                       std::move(launch_callback));
+                       login_item_restore_state, std::move(launch_callback));
       break;
     }
     case chrome::mojom::AppShimLaunchType::kRegisterOnly:
@@ -385,11 +387,13 @@
     const web_app::AppId& app_id,
     const base::FilePath& profile_path,
     const std::vector<base::FilePath>& launch_files,
+    chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state,
     LoadAndLaunchAppCallback launch_callback) {
   // Check to see if the app is already running for a profile compatible with
   // |profile_path|. If so, early-out.
   if (LoadAndLaunchApp_TryExistingProfileStates(
-          app_id, profile_path, launch_files, &launch_callback)) {
+          app_id, profile_path, launch_files, login_item_restore_state,
+          &launch_callback)) {
     // If we used an existing profile, |launch_callback| should have been run.
     DCHECK(!launch_callback);
     return;
@@ -418,7 +422,8 @@
   base::OnceClosure callback =
       base::BindOnce(&AppShimManager::LoadAndLaunchApp_OnProfilesAndAppReady,
                      weak_factory_.GetWeakPtr(), app_id, launch_files,
-                     profile_paths_to_launch, std::move(launch_callback));
+                     login_item_restore_state, profile_paths_to_launch,
+                     std::move(launch_callback));
   {
     // This will update |callback| to be a chain of callbacks that load the
     // profiles in |profile_paths_to_load|, one by one, using
@@ -444,6 +449,7 @@
     const web_app::AppId& app_id,
     const base::FilePath& profile_path,
     const std::vector<base::FilePath>& launch_files,
+    chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state,
     LoadAndLaunchAppCallback* launch_callback) {
   auto found_app = apps_.find(app_id);
   if (found_app == apps_.end())
@@ -477,7 +483,7 @@
 
   // Launch the app, if appropriate.
   LoadAndLaunchApp_LaunchIfAppropriate(profile, profile_state, app_id,
-                                       launch_files);
+                                       launch_files, login_item_restore_state);
 
   std::move(*launch_callback)
       .Run(profile_state, chrome::mojom::AppShimLaunchResult::kSuccess);
@@ -487,6 +493,7 @@
 void AppShimManager::LoadAndLaunchApp_OnProfilesAndAppReady(
     const web_app::AppId& app_id,
     const std::vector<base::FilePath>& launch_files,
+    chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state,
     const std::vector<base::FilePath>& profile_paths_to_launch,
     LoadAndLaunchAppCallback launch_callback) {
   // Launch all of the profiles in |profile_paths_to_launch|. Record the most
@@ -519,8 +526,8 @@
       profile_state = GetOrCreateProfileState(profile, app_id);
 
     // Launch the app, if appropriate.
-    LoadAndLaunchApp_LaunchIfAppropriate(profile, profile_state, app_id,
-                                         launch_files);
+    LoadAndLaunchApp_LaunchIfAppropriate(
+        profile, profile_state, app_id, launch_files, login_item_restore_state);
 
     // If we successfully created a profile state, save it for |bootstrap| to
     // connect to once all launches are done.
@@ -602,7 +609,8 @@
     Profile* profile,
     ProfileState* profile_state,
     const web_app::AppId& app_id,
-    const std::vector<base::FilePath>& launch_files) {
+    const std::vector<base::FilePath>& launch_files,
+    chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state) {
   // If |launch_files| is non-empty, then always do a launch to open the
   // files.
   bool do_launch = !launch_files.empty();
@@ -618,7 +626,8 @@
   }
 
   if (do_launch)
-    delegate_->LaunchApp(profile, app_id, launch_files);
+    delegate_->LaunchApp(profile, app_id, launch_files,
+                         login_item_restore_state);
 }
 
 // static
@@ -826,7 +835,8 @@
   LoadAndLaunchApp(
       host->GetAppId(),
       app_state->IsMultiProfile() ? base::FilePath() : host->GetProfilePath(),
-      std::vector<base::FilePath>(), base::DoNothing());
+      std::vector<base::FilePath>(),
+      chrome::mojom::AppShimLoginItemRestoreState::kNone, base::DoNothing());
 }
 
 void AppShimManager::OnShimOpenedFiles(
@@ -838,13 +848,15 @@
   LoadAndLaunchApp(
       host->GetAppId(),
       app_state->IsMultiProfile() ? base::FilePath() : host->GetProfilePath(),
-      files, base::DoNothing());
+      files, chrome::mojom::AppShimLoginItemRestoreState::kNone,
+      base::DoNothing());
 }
 
 void AppShimManager::OnShimSelectedProfile(AppShimHost* host,
                                            const base::FilePath& profile_path) {
-  LoadAndLaunchApp(host->GetAppId(), profile_path,
-                   std::vector<base::FilePath>(), base::DoNothing());
+  LoadAndLaunchApp(
+      host->GetAppId(), profile_path, std::vector<base::FilePath>(),
+      chrome::mojom::AppShimLoginItemRestoreState::kNone, base::DoNothing());
 }
 
 void AppShimManager::OnProfileAdded(Profile* profile) {
diff --git a/chrome/browser/apps/app_shim/app_shim_manager_mac.h b/chrome/browser/apps/app_shim/app_shim_manager_mac.h
index ed0e4fc..e89ecd9a 100644
--- a/chrome/browser/apps/app_shim/app_shim_manager_mac.h
+++ b/chrome/browser/apps/app_shim/app_shim_manager_mac.h
@@ -91,7 +91,9 @@
     // is called.
     virtual void LaunchApp(Profile* profile,
                            const web_app::AppId& app_id,
-                           const std::vector<base::FilePath>& files) = 0;
+                           const std::vector<base::FilePath>& files,
+                           chrome::mojom::AppShimLoginItemRestoreState
+                               login_item_restore_state) = 0;
 
     // Launch the shim process for an app. It is guaranteed that |app_id| is
     // installed for |profile| when this method is called.
@@ -257,25 +259,30 @@
   using LoadAndLaunchAppCallback =
       base::OnceCallback<void(ProfileState* profile_state,
                               chrome::mojom::AppShimLaunchResult result)>;
-  void LoadAndLaunchApp(const web_app::AppId& app_id,
-                        const base::FilePath& profile_path,
-                        const std::vector<base::FilePath>& launch_files,
-                        LoadAndLaunchAppCallback launch_callback);
+  void LoadAndLaunchApp(
+      const web_app::AppId& app_id,
+      const base::FilePath& profile_path,
+      const std::vector<base::FilePath>& launch_files,
+      chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state,
+      LoadAndLaunchAppCallback launch_callback);
   bool LoadAndLaunchApp_TryExistingProfileStates(
       const web_app::AppId& app_id,
       const base::FilePath& profile_path,
       const std::vector<base::FilePath>& launch_files,
+      chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state,
       LoadAndLaunchAppCallback* launch_callback);
   void LoadAndLaunchApp_OnProfilesAndAppReady(
       const web_app::AppId& app_id,
       const std::vector<base::FilePath>& launch_files,
+      chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state,
       const std::vector<base::FilePath>& profile_paths_to_launch,
       LoadAndLaunchAppCallback launch_callback);
   void LoadAndLaunchApp_LaunchIfAppropriate(
       Profile* profile,
       ProfileState* profile_state,
       const web_app::AppId& app_id,
-      const std::vector<base::FilePath>& launch_files);
+      const std::vector<base::FilePath>& launch_files,
+      chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state);
 
   // The final step of both paths for OnShimProcessConnected. This will connect
   // |bootstrap| to |profile_state|'s AppShimHost, if possible. The value of
diff --git a/chrome/browser/apps/app_shim/app_shim_manager_mac_unittest.cc b/chrome/browser/apps/app_shim/app_shim_manager_mac_unittest.cc
index 1326cfa..c0d930d 100644
--- a/chrome/browser/apps/app_shim/app_shim_manager_mac_unittest.cc
+++ b/chrome/browser/apps/app_shim/app_shim_manager_mac_unittest.cc
@@ -53,10 +53,11 @@
   MOCK_METHOD2(AppIsMultiProfile, bool(Profile*, const std::string&));
   MOCK_METHOD3(EnableExtension,
                void(Profile*, const std::string&, base::OnceCallback<void()>));
-  MOCK_METHOD3(LaunchApp,
+  MOCK_METHOD4(LaunchApp,
                void(Profile*,
                     const std::string& app_id,
-                    const std::vector<base::FilePath>&));
+                    const std::vector<base::FilePath>&,
+                    chrome::mojom::AppShimLoginItemRestoreState));
 
   // Conditionally mock LaunchShim. Some tests will execute |launch_callback|
   // with a particular value.
@@ -185,8 +186,10 @@
   TestingAppShimHostBootstrap& operator=(const TestingAppShimHostBootstrap&) =
       delete;
 
-  void DoTestLaunch(chrome::mojom::AppShimLaunchType launch_type,
-                    const std::vector<base::FilePath>& files) {
+  void DoTestLaunch(
+      chrome::mojom::AppShimLaunchType launch_type,
+      const std::vector<base::FilePath>& files,
+      chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state) {
     mojo::Remote<chrome::mojom::AppShimHost> host;
     auto app_shim_info = chrome::mojom::AppShimInfo::New();
     app_shim_info->profile_path = profile_path_;
@@ -195,6 +198,7 @@
       app_shim_info->app_url = GURL("https://example.com");
     app_shim_info->launch_type = launch_type;
     app_shim_info->files = files;
+    app_shim_info->login_item_restore_state = login_item_restore_state;
     OnShimConnected(
         host.BindNewPipeAndPassReceiver(), std::move(app_shim_info),
         base::BindOnce(&TestingAppShimHostBootstrap::DoTestLaunchDone,
@@ -411,7 +415,7 @@
         .WillRepeatedly(Return(false));
     EXPECT_CALL(*delegate_, AppIsInstalled(_, kTestAppIdB))
         .WillRepeatedly(Return(true));
-    EXPECT_CALL(*delegate_, LaunchApp(_, _, _)).WillRepeatedly(Return());
+    EXPECT_CALL(*delegate_, LaunchApp(_, _, _, _)).WillRepeatedly(Return());
   }
 
   void TearDown() override {
@@ -440,27 +444,31 @@
         nullptr, base::FilePath());
   }
 
-  void DoShimLaunch(base::WeakPtr<TestingAppShimHostBootstrap> bootstrap,
-                    std::unique_ptr<TestHost> host,
-                    chrome::mojom::AppShimLaunchType launch_type,
-                    const std::vector<base::FilePath>& files) {
+  void DoShimLaunch(
+      base::WeakPtr<TestingAppShimHostBootstrap> bootstrap,
+      std::unique_ptr<TestHost> host,
+      chrome::mojom::AppShimLaunchType launch_type,
+      const std::vector<base::FilePath>& files,
+      chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state) {
     if (host)
       manager_->SetHostForCreate(std::move(host));
-    bootstrap->DoTestLaunch(launch_type, files);
+    bootstrap->DoTestLaunch(launch_type, files, login_item_restore_state);
   }
 
   void NormalLaunch(base::WeakPtr<TestingAppShimHostBootstrap> bootstrap,
                     std::unique_ptr<TestHost> host) {
     DoShimLaunch(bootstrap, std::move(host),
                  chrome::mojom::AppShimLaunchType::kNormal,
-                 std::vector<base::FilePath>());
+                 std::vector<base::FilePath>(),
+                 chrome::mojom::AppShimLoginItemRestoreState::kNone);
   }
 
   void RegisterOnlyLaunch(base::WeakPtr<TestingAppShimHostBootstrap> bootstrap,
                           std::unique_ptr<TestHost> host) {
     DoShimLaunch(bootstrap, std::move(host),
                  chrome::mojom::AppShimLaunchType::kRegisterOnly,
-                 std::vector<base::FilePath>());
+                 std::vector<base::FilePath>(),
+                 chrome::mojom::AppShimLoginItemRestoreState::kNone);
   }
 
   // Completely launch a shim host and leave it running.
@@ -596,9 +604,12 @@
   EXPECT_EQ(host_ab_.get(), manager_->FindHost(&profile_a_, kTestAppIdB));
 
   std::vector<base::FilePath> some_file(1, base::FilePath("some_file"));
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_b_, kTestAppIdB, some_file));
+  EXPECT_CALL(*delegate_,
+              LaunchApp(&profile_b_, kTestAppIdB, some_file,
+                        chrome::mojom::AppShimLoginItemRestoreState::kNone));
   DoShimLaunch(bootstrap_bb_, std::move(host_bb_unique_),
-               chrome::mojom::AppShimLaunchType::kNormal, some_file);
+               chrome::mojom::AppShimLaunchType::kNormal, some_file,
+               chrome::mojom::AppShimLoginItemRestoreState::kNone);
   EXPECT_EQ(host_bb_.get(), manager_->FindHost(&profile_b_, kTestAppIdB));
 
   // Activation when there is a registered shim finishes launch with success and
@@ -610,7 +621,47 @@
 
   // Starting and closing a second host does nothing.
   DoShimLaunch(bootstrap_aa_duplicate_, std::move(host_aa_duplicate_unique_),
-               chrome::mojom::AppShimLaunchType::kNormal, some_file);
+               chrome::mojom::AppShimLaunchType::kNormal, some_file,
+               chrome::mojom::AppShimLoginItemRestoreState::kNone);
+  EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kDuplicateHost,
+            *bootstrap_aa_duplicate_result_);
+  EXPECT_EQ(host_aa_.get(), manager_->FindHost(&profile_a_, kTestAppIdA));
+
+  // Normal close.
+  manager_->OnShimProcessDisconnected(host_aa_.get());
+  EXPECT_FALSE(manager_->FindHost(&profile_a_, kTestAppIdA));
+  EXPECT_EQ(host_aa_.get(), nullptr);
+}
+
+TEST_F(AppShimManagerTest, RunOnOsLoginLaunchAndCloseShim) {
+  NormalLaunch(bootstrap_aa_, std::move(host_aa_unique_));
+  EXPECT_EQ(host_aa_.get(), manager_->FindHost(&profile_a_, kTestAppIdA));
+
+  NormalLaunch(bootstrap_ab_, std::move(host_ab_unique_));
+  EXPECT_EQ(host_ab_.get(), manager_->FindHost(&profile_a_, kTestAppIdB));
+
+  // Run on OS Login Launch
+  std::vector<base::FilePath> some_file(1, base::FilePath("some_file"));
+  EXPECT_CALL(
+      *delegate_,
+      LaunchApp(&profile_b_, kTestAppIdB, some_file,
+                chrome::mojom::AppShimLoginItemRestoreState::kWindowed));
+  DoShimLaunch(bootstrap_bb_, std::move(host_bb_unique_),
+               chrome::mojom::AppShimLaunchType::kNormal, some_file,
+               chrome::mojom::AppShimLoginItemRestoreState::kWindowed);
+  EXPECT_EQ(host_bb_.get(), manager_->FindHost(&profile_b_, kTestAppIdB));
+
+  // Activation when there is a registered shim finishes launch with success and
+  // focuses the app.
+  EXPECT_CALL(*manager_, OnShimFocus(host_aa_.get()));
+  manager_->OnAppActivated(&profile_a_, kTestAppIdA);
+  EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kSuccess,
+            *bootstrap_aa_result_);
+
+  // Starting and closing a second host does nothing.
+  DoShimLaunch(bootstrap_aa_duplicate_, std::move(host_aa_duplicate_unique_),
+               chrome::mojom::AppShimLaunchType::kNormal, some_file,
+               chrome::mojom::AppShimLoginItemRestoreState::kNone);
   EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kDuplicateHost,
             *bootstrap_aa_duplicate_result_);
   EXPECT_EQ(host_aa_.get(), manager_->FindHost(&profile_a_, kTestAppIdA));
@@ -638,7 +689,7 @@
 
   // Normal shim launch adds an entry in the map.
   // App should not be launched here, but return success to the shim.
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _)).Times(0);
+  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _, _)).Times(0);
   RegisterOnlyLaunch(bootstrap_aa_, nullptr);
   EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kSuccess,
             *bootstrap_aa_result_);
@@ -647,24 +698,29 @@
   // Return no app windows for OnShimFocus. This will do nothing.
   EXPECT_CALL(*delegate_, ShowAppWindows(&profile_a_, kTestAppIdA))
       .WillRepeatedly(Return(false));
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _)).Times(0);
+  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _, _)).Times(0);
   ShimNormalFocus(host_aa_.get());
 
   // Return no app windows for OnShimReopen. This will result in a launch call.
   EXPECT_CALL(*delegate_, ShowAppWindows(&profile_a_, kTestAppIdA))
       .WillRepeatedly(Return(false));
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _)).Times(1);
+  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _, _)).Times(1);
   host_aa_->ReopenApp();
 
   // Return one window. This should do nothing.
   EXPECT_CALL(*delegate_, ShowAppWindows(&profile_a_, kTestAppIdA))
       .WillRepeatedly(Return(true));
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _)).Times(0);
+  EXPECT_CALL(*delegate_,
+              LaunchApp(&profile_a_, kTestAppIdA, _,
+                        chrome::mojom::AppShimLoginItemRestoreState::kNone))
+      .Times(0);
   host_aa_->ReopenApp();
 
   // Open files should trigger a launch with those files.
   std::vector<base::FilePath> some_file(1, base::FilePath("some_file"));
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, some_file));
+  EXPECT_CALL(*delegate_,
+              LaunchApp(&profile_a_, kTestAppIdA, some_file,
+                        chrome::mojom::AppShimLoginItemRestoreState::kNone));
   host_aa_->FilesOpened(some_file);
 
   // OnAppDeactivated should not close the shim.
@@ -691,7 +747,7 @@
 
   // Normal shim launch adds an entry in the map.
   // App should not be launched here, but return success to the shim.
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _)).Times(0);
+  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _, _)).Times(0);
   RegisterOnlyLaunch(bootstrap_aa_, nullptr);
   EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kSuccess,
             *bootstrap_aa_result_);
@@ -700,24 +756,24 @@
   // Return no app windows for OnShimFocus. This will do nothing.
   EXPECT_CALL(*delegate_, ShowAppWindows(&profile_a_, kTestAppIdA))
       .WillRepeatedly(Return(false));
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _)).Times(0);
+  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _, _)).Times(0);
   ShimNormalFocus(host_aa_.get());
 
   // Return no app windows for OnShimReopen. This will result in a launch call.
   EXPECT_CALL(*delegate_, ShowAppWindows(&profile_a_, kTestAppIdA))
       .WillRepeatedly(Return(false));
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _)).Times(1);
+  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _, _)).Times(1);
   host_aa_->ReopenApp();
 
   // Return one window. This should do nothing.
   EXPECT_CALL(*delegate_, ShowAppWindows(&profile_a_, kTestAppIdA))
       .WillRepeatedly(Return(true));
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _)).Times(0);
+  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _, _)).Times(0);
   host_aa_->ReopenApp();
 
   // Open files should trigger a launch with those files.
   std::vector<base::FilePath> some_file(1, base::FilePath("some_file"));
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, some_file));
+  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, some_file, _));
   host_aa_->FilesOpened(some_file);
 
   // Process disconnect will cause the host to be deleted.
@@ -915,7 +971,7 @@
 TEST_F(AppShimManagerTest, RegisterOnly) {
   // For an chrome::mojom::AppShimLaunchType::kRegisterOnly, don't launch the
   // app.
-  EXPECT_CALL(*delegate_, LaunchApp(_, _, _)).Times(0);
+  EXPECT_CALL(*delegate_, LaunchApp(_, _, _, _)).Times(0);
   RegisterOnlyLaunch(bootstrap_aa_, std::move(host_aa_unique_));
   EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kSuccess,
             *bootstrap_aa_result_);
@@ -930,7 +986,7 @@
   delegate_->SetAppCanCreateHost(false);
 
   // The app should be launched.
-  EXPECT_CALL(*delegate_, LaunchApp(_, _, _)).Times(1);
+  EXPECT_CALL(*delegate_, LaunchApp(_, _, _, _)).Times(1);
   NormalLaunch(bootstrap_ab_, std::move(host_ab_unique_));
   // But the bootstrap should be closed.
   EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kSuccessAndDisconnect,
@@ -982,11 +1038,15 @@
 
   // Launch the app for this host. It should find the pre-existing host, and the
   // pre-existing host's launch result should be set.
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _)).Times(0);
+  EXPECT_CALL(*delegate_,
+              LaunchApp(&profile_a_, kTestAppIdA, _,
+                        chrome::mojom::AppShimLoginItemRestoreState::kNone))
+      .Times(0);
   EXPECT_FALSE(host_aa_->did_connect_to_host());
   DoShimLaunch(bootstrap_aa_, nullptr,
                chrome::mojom::AppShimLaunchType::kRegisterOnly,
-               std::vector<base::FilePath>());
+               std::vector<base::FilePath>(),
+               chrome::mojom::AppShimLoginItemRestoreState::kNone);
   EXPECT_TRUE(host_aa_->did_connect_to_host());
   EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kSuccess,
             *bootstrap_aa_result_);
@@ -996,7 +1056,8 @@
   // profile should remain.
   DoShimLaunch(bootstrap_aa_duplicate_, nullptr,
                chrome::mojom::AppShimLaunchType::kRegisterOnly,
-               std::vector<base::FilePath>());
+               std::vector<base::FilePath>(),
+               chrome::mojom::AppShimLoginItemRestoreState::kNone);
   EXPECT_TRUE(host_aa_->did_connect_to_host());
   EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kDuplicateHost,
             *bootstrap_aa_duplicate_result_);
@@ -1091,7 +1152,9 @@
 
   // Select profile B from the menu. This should request that the app be
   // launched.
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_b_, kTestAppIdA, _));
+  EXPECT_CALL(*delegate_,
+              LaunchApp(&profile_b_, kTestAppIdA, _,
+                        chrome::mojom::AppShimLoginItemRestoreState::kNone));
   host_aa_->ProfileSelectedFromMenu(profile_path_b_);
   EXPECT_CALL(*delegate_, DoLaunchShim(_, _, _)).Times(0);
   manager_->OnAppActivated(&profile_b_, kTestAppIdA);
@@ -1099,7 +1162,10 @@
   // Select profile A and B from the menu -- this should not request a launch,
   // because the profiles are already enabled.
   EXPECT_CALL(*delegate_, ShowAppWindows(_, _)).WillRepeatedly(Return(true));
-  EXPECT_CALL(*delegate_, LaunchApp(_, _, _)).Times(0);
+  EXPECT_CALL(
+      *delegate_,
+      LaunchApp(_, _, _, chrome::mojom::AppShimLoginItemRestoreState::kNone))
+      .Times(0);
   host_aa_->ProfileSelectedFromMenu(profile_path_a_);
   host_aa_->ProfileSelectedFromMenu(profile_path_b_);
 }
@@ -1127,7 +1193,10 @@
   EXPECT_EQ(host_aa_.get(), manager_->FindHost(&profile_a_, kTestAppIdA));
 
   // Launch the shim.
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _)).Times(0);
+  EXPECT_CALL(*delegate_,
+              LaunchApp(&profile_a_, kTestAppIdA, _,
+                        chrome::mojom::AppShimLoginItemRestoreState::kNone))
+      .Times(0);
   RegisterOnlyLaunch(bootstrap_aa_, nullptr);
   EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kSuccess,
             *bootstrap_aa_result_);
@@ -1181,8 +1250,14 @@
 
   // Launch the shim requesting profile C.
   manager_->SetHostForCreate(std::move(host_aa_unique_));
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _)).Times(1);
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_b_, kTestAppIdA, _)).Times(0);
+  EXPECT_CALL(*delegate_,
+              LaunchApp(&profile_a_, kTestAppIdA, _,
+                        chrome::mojom::AppShimLoginItemRestoreState::kNone))
+      .Times(1);
+  EXPECT_CALL(*delegate_,
+              LaunchApp(&profile_b_, kTestAppIdA, _,
+                        chrome::mojom::AppShimLoginItemRestoreState::kNone))
+      .Times(0);
   EXPECT_CALL(*delegate_, EnableExtension(&profile_c_, kTestAppIdA, _))
       .WillOnce(RunOnceCallback<2>());
   NormalLaunch(bootstrap_ca_, nullptr);
@@ -1198,8 +1273,14 @@
 
   // Launch the shim without specifying a profile.
   manager_->SetHostForCreate(std::move(host_aa_unique_));
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_a_, kTestAppIdA, _)).Times(1);
-  EXPECT_CALL(*delegate_, LaunchApp(&profile_b_, kTestAppIdA, _)).Times(0);
+  EXPECT_CALL(*delegate_,
+              LaunchApp(&profile_a_, kTestAppIdA, _,
+                        chrome::mojom::AppShimLoginItemRestoreState::kNone))
+      .Times(1);
+  EXPECT_CALL(*delegate_,
+              LaunchApp(&profile_b_, kTestAppIdA, _,
+                        chrome::mojom::AppShimLoginItemRestoreState::kNone))
+      .Times(0);
   NormalLaunch(bootstrap_xa_, nullptr);
   EXPECT_EQ(chrome::mojom::AppShimLaunchResult::kSuccess,
             *bootstrap_xa_result_);
diff --git a/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.cc b/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.cc
index cd7350ff..10c311e 100644
--- a/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.cc
+++ b/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/web_applications/components/os_integration_manager.h"
 #include "chrome/browser/web_applications/components/web_app_shortcut_mac.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/common/chrome_switches.h"
 
 namespace web_app {
 
@@ -86,20 +87,30 @@
 void WebAppShimManagerDelegate::LaunchApp(
     Profile* profile,
     const AppId& app_id,
-    const std::vector<base::FilePath>& files) {
+    const std::vector<base::FilePath>& files,
+    chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state) {
   DCHECK(AppIsInstalled(profile, app_id));
   if (UseFallback(profile, app_id)) {
-    fallback_delegate_->LaunchApp(profile, app_id, files);
+    fallback_delegate_->LaunchApp(profile, app_id, files,
+                                  login_item_restore_state);
     return;
   }
   DisplayMode display_mode =
       WebAppProvider::Get(profile)->registrar().GetAppUserDisplayMode(app_id);
   apps::mojom::LaunchContainer launch_container =
       web_app::ConvertDisplayModeToAppLaunchContainer(display_mode);
-  apps::AppLaunchParams params(
-      app_id, launch_container, WindowOpenDisposition::NEW_FOREGROUND_TAB,
-      apps::mojom::AppLaunchSource::kSourceCommandLine);
+  apps::mojom::AppLaunchSource launch_source =
+      apps::mojom::AppLaunchSource::kSourceCommandLine;
+  if (login_item_restore_state !=
+      chrome::mojom::AppShimLoginItemRestoreState::kNone) {
+    launch_source = apps::mojom::AppLaunchSource::kSourceRunOnOsLogin;
+  }
+
+  apps::AppLaunchParams params(app_id, launch_container,
+                               WindowOpenDisposition::NEW_FOREGROUND_TAB,
+                               launch_source);
   params.launch_files = files;
+
   apps::AppServiceProxyFactory::GetForProfile(profile)
       ->BrowserAppLauncher()
       ->LaunchAppWithParams(std::move(params));
diff --git a/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.h b/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.h
index eaf42b4d..793be87 100644
--- a/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.h
+++ b/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.h
@@ -26,7 +26,9 @@
                        base::OnceCallback<void()> callback) override;
   void LaunchApp(Profile* profile,
                  const AppId& app_id,
-                 const std::vector<base::FilePath>& files) override;
+                 const std::vector<base::FilePath>& files,
+                 chrome::mojom::AppShimLoginItemRestoreState
+                     login_item_restore_state) override;
   void LaunchShim(Profile* profile,
                   const AppId& app_id,
                   bool recreate_shims,
diff --git a/chrome/browser/apps/platform_apps/extension_app_shim_manager_delegate_mac.cc b/chrome/browser/apps/platform_apps/extension_app_shim_manager_delegate_mac.cc
index 38b898d..b466228 100644
--- a/chrome/browser/apps/platform_apps/extension_app_shim_manager_delegate_mac.cc
+++ b/chrome/browser/apps/platform_apps/extension_app_shim_manager_delegate_mac.cc
@@ -186,7 +186,8 @@
 void ExtensionAppShimManagerDelegate::LaunchApp(
     Profile* profile,
     const web_app::AppId& app_id,
-    const std::vector<base::FilePath>& files) {
+    const std::vector<base::FilePath>& files,
+    chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state) {
   const Extension* extension = MaybeGetAppExtension(profile, app_id);
   DCHECK(extension);
   extensions::RecordAppLaunchType(extension_misc::APP_LAUNCH_CMD_LINE_APP,
diff --git a/chrome/browser/apps/platform_apps/extension_app_shim_manager_delegate_mac.h b/chrome/browser/apps/platform_apps/extension_app_shim_manager_delegate_mac.h
index d641c44..35aabbe 100644
--- a/chrome/browser/apps/platform_apps/extension_app_shim_manager_delegate_mac.h
+++ b/chrome/browser/apps/platform_apps/extension_app_shim_manager_delegate_mac.h
@@ -28,7 +28,9 @@
                        base::OnceCallback<void()> callback) override;
   void LaunchApp(Profile* profile,
                  const web_app::AppId& app_id,
-                 const std::vector<base::FilePath>& files) override;
+                 const std::vector<base::FilePath>& files,
+                 chrome::mojom::AppShimLoginItemRestoreState
+                     login_item_restore_state) override;
   void LaunchShim(Profile* profile,
                   const web_app::AppId& app_id,
                   bool recreate_shims,
diff --git a/chrome/browser/ash/login/screens/welcome_screen.cc b/chrome/browser/ash/login/screens/welcome_screen.cc
index 20927e2..b5957363 100644
--- a/chrome/browser/ash/login/screens/welcome_screen.cc
+++ b/chrome/browser/ash/login/screens/welcome_screen.cc
@@ -11,6 +11,7 @@
 #include "ash/constants/ash_switches.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
+#include "base/command_line.h"
 #include "base/containers/contains.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_functions.h"
@@ -35,6 +36,8 @@
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
+#include "chromeos/components/chromebox_for_meetings/buildflags/buildflags.h"
+#include "chromeos/dbus/constants/dbus_switches.h"
 #include "components/language/core/browser/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_thread.h"
@@ -42,6 +45,7 @@
 namespace chromeos {
 namespace {
 
+constexpr const char kRemoraRequisitionIdentifier[] = "remora";
 constexpr char kUserActionContinueButtonClicked[] = "continue";
 constexpr const char kUserActionEnableSpokenFeedback[] =
     "accessibility-spoken-feedback-enable";
@@ -142,6 +146,18 @@
   NOTREACHED() << "Unexpected action id: " << action_id;
 }
 
+// Returns true if is a Meet Device or the remora requisition bit has been set
+// for testing. Note: Can be overridden with the command line switch
+// --enable-requisition-edits.
+bool IsRemoraRequisitionConfigurable() {
+#if BUILDFLAG(PLATFORM_CFM)
+  return true;
+#else
+  return policy::EnrollmentRequisitionManager::IsRemoraRequisition() ||
+         chromeos::switches::IsDeviceRequisitionConfigurable();
+#endif
+}
+
 }  // namespace
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -281,6 +297,14 @@
 }
 
 void WelcomeScreen::SetDeviceRequisition(const std::string& requisition) {
+  if (requisition == kRemoraRequisitionIdentifier) {
+    if (!IsRemoraRequisitionConfigurable())
+      return;
+  } else {
+    if (!switches::IsDeviceRequisitionConfigurable())
+      return;
+  }
+
   std::string initial_requisition =
       policy::EnrollmentRequisitionManager::GetDeviceRequisition();
   policy::EnrollmentRequisitionManager::SetDeviceRequisition(requisition);
@@ -454,12 +478,14 @@
   } else if (action == ash::LoginAcceleratorAction::kEnableDebugging) {
     OnEnableDebugging();
     return true;
-  } else if (action == ash::LoginAcceleratorAction::kEditDeviceRequisition) {
+  } else if (action == ash::LoginAcceleratorAction::kEditDeviceRequisition &&
+             switches::IsDeviceRequisitionConfigurable()) {
     if (view_)
       view_->ShowEditRequisitionDialog(
           policy::EnrollmentRequisitionManager::GetDeviceRequisition());
     return true;
-  } else if (action == ash::LoginAcceleratorAction::kDeviceRequisitionRemora) {
+  } else if (action == ash::LoginAcceleratorAction::kDeviceRequisitionRemora &&
+             IsRemoraRequisitionConfigurable()) {
     if (view_)
       view_->ShowRemoraRequisitionDialog();
     return true;
@@ -562,6 +588,14 @@
     return;
   }
 
+  // This is done so that developers and testers don't repeatedly receive
+  // the hint when flashing.
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kSystemDevMode) &&
+      !chromeos::switches::IsOOBEChromeVoxHintEnabledForDevMode()) {
+    return;
+  }
+
   if (chromevox_hint_timer_activated_)
     return;
 
diff --git a/chrome/browser/ash/login/screens/welcome_screen.h b/chrome/browser/ash/login/screens/welcome_screen.h
index 9b1b951..dcf9615 100644
--- a/chrome/browser/ash/login/screens/welcome_screen.h
+++ b/chrome/browser/ash/login/screens/welcome_screen.h
@@ -112,6 +112,9 @@
   bool GetChromeVoxHintTimerCancelledForTesting() {
     return chromevox_hint_timer_cancelled_for_testing_;
   }
+  bool GetChromeVoxHintTimerActivatedForTesting() {
+    return chromevox_hint_timer_activated_;
+  }
 
  protected:
   // Exposes exit callback to test overrides.
diff --git a/chrome/browser/ash/login/screens/welcome_screen_browsertest.cc b/chrome/browser/ash/login/screens/welcome_screen_browsertest.cc
index 5f8151b..0c1bcee 100644
--- a/chrome/browser/ash/login/screens/welcome_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/welcome_screen_browsertest.cc
@@ -5,6 +5,7 @@
 #include <memory>
 
 #include "ash/constants/ash_paths.h"
+#include "ash/constants/ash_switches.h"
 #include "ash/public/cpp/test/shell_test_api.h"
 #include "base/bind.h"
 #include "base/files/file_util.h"
@@ -556,6 +557,9 @@
 // Clicking the 'activate' button in the dialog should activate ChromeVox.
 IN_PROC_BROWSER_TEST_F(WelcomeScreenChromeVoxHintTest, LaptopClick) {
   OobeScreenWaiter(WelcomeView::kScreenId).Wait();
+  // A sanity check to ensure the ChromeVox hint timer is disabled for this and
+  // similar tests.
+  ASSERT_FALSE(welcome_screen()->GetChromeVoxHintTimerActivatedForTesting());
   TtsExtensionEngine::GetInstance()->DisableBuiltInTTSEngineForTesting();
   test::ExecuteOobeJS(kSetAvailableVoices);
   test::SpeechMonitor monitor;
@@ -822,4 +826,54 @@
   WaitForSpokenSuccessMetric();
 }
 
+// Tests the behavior of the ChromeVox hint in dev mode without the enabling
+// flag.
+class WelcomeScreenChromeVoxHintDevModeTest : public WelcomeScreenBrowserTest {
+ public:
+  WelcomeScreenChromeVoxHintDevModeTest() = default;
+  ~WelcomeScreenChromeVoxHintDevModeTest() override = default;
+
+  // WelcomeScreenBrowserTest:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    WelcomeScreenBrowserTest::SetUpCommandLine(command_line);
+    command_line->AppendSwitch(chromeos::switches::kSystemDevMode);
+    command_line->RemoveSwitch(
+        chromeos::switches::kDisableOOBEChromeVoxHintTimerForTesting);
+  }
+};
+
+// The ChromeVox hint timer should not be activated if the device is in dev
+// mode without the enabling flag.
+IN_PROC_BROWSER_TEST_F(WelcomeScreenChromeVoxHintDevModeTest,
+                       TimerNotActivated) {
+  OobeScreenWaiter(WelcomeView::kScreenId).Wait();
+  EXPECT_FALSE(welcome_screen()->GetChromeVoxHintTimerActivatedForTesting());
+}
+
+// Tests the behavior of the ChromeVox hint in dev mode with the enabling flag.
+class WelcomeScreenChromeVoxHintDevModeWithFlagTest
+    : public WelcomeScreenBrowserTest {
+ public:
+  WelcomeScreenChromeVoxHintDevModeWithFlagTest() = default;
+  ~WelcomeScreenChromeVoxHintDevModeWithFlagTest() override = default;
+
+  // WelcomeScreenBrowserTest:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    WelcomeScreenBrowserTest::SetUpCommandLine(command_line);
+    command_line->AppendSwitch(chromeos::switches::kSystemDevMode);
+    command_line->AppendSwitch(
+        chromeos::switches::kEnableOOBEChromeVoxHintForDevMode);
+    command_line->RemoveSwitch(
+        chromeos::switches::kDisableOOBEChromeVoxHintTimerForTesting);
+  }
+};
+
+// The ChromeVox hint timer should be activated if the device is in dev mode and
+// the enabling flag is specified on the command line.
+IN_PROC_BROWSER_TEST_F(WelcomeScreenChromeVoxHintDevModeWithFlagTest,
+                       TimerActivated) {
+  OobeScreenWaiter(WelcomeView::kScreenId).Wait();
+  EXPECT_TRUE(welcome_screen()->GetChromeVoxHintTimerActivatedForTesting());
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/ash/login/session/user_session_manager.cc b/chrome/browser/ash/login/session/user_session_manager.cc
index d4db3bb5..2cb4e28 100644
--- a/chrome/browser/ash/login/session/user_session_manager.cc
+++ b/chrome/browser/ash/login/session/user_session_manager.cc
@@ -37,6 +37,7 @@
 #include "chrome/browser/app_mode/app_mode_utils.h"
 #include "chrome/browser/ash/account_manager/account_manager_migrator.h"
 #include "chrome/browser/ash/account_manager/account_manager_util.h"
+#include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/ash/login/auth/chrome_cryptohome_authenticator.h"
 #include "chrome/browser/ash/login/chrome_restart_request.h"
 #include "chrome/browser/ash/login/demo_mode/demo_app_launcher.h"
@@ -1883,6 +1884,15 @@
     return;
   }
 
+  if (sessions->size() > 1 && crosapi::browser_util::IsLacrosEnabled()) {
+    LOG(ERROR) << "Multiple sessions are disabled when Lacros is enabled";
+    // If we could not get list of active user sessions it is safer to just
+    // sign out so that we don't get in the inconsistent state.
+    SessionTerminationManager::Get()->StopSession(
+        login_manager::SessionStopReason::RESTORE_ACTIVE_SESSIONS);
+    return;
+  }
+
   // One profile has been already loaded on browser start.
   user_manager::UserManager* user_manager = user_manager::UserManager::Get();
   DCHECK_EQ(1u, user_manager->GetLoggedInUsers().size());
@@ -1967,6 +1977,10 @@
   user_sessions_restore_in_progress_ = false;
   for (auto& observer : session_state_observer_list_)
     observer.PendingUserSessionsRestoreFinished();
+
+  if (crosapi::browser_util::IsLacrosEnabled()) {
+    CHECK_LE(user_manager::UserManager::Get()->GetLoggedInUsers().size(), 1);
+  }
 }
 
 void UserSessionManager::UpdateEasyUnlockKeys(const UserContext& user_context) {
diff --git a/chrome/browser/ash/login/signin_partition_manager.cc b/chrome/browser/ash/login/signin_partition_manager.cc
index dfb87f4..cfea901 100644
--- a/chrome/browser/ash/login/signin_partition_manager.cc
+++ b/chrome/browser/ash/login/signin_partition_manager.cc
@@ -92,8 +92,8 @@
   current_storage_partition_name_ = GeneratePartitionName();
 
   auto storage_partition_config = content::StoragePartitionConfig::Create(
-      storage_partition_domain_, current_storage_partition_name_,
-      true /*in_memory */);
+      browser_context_, storage_partition_domain_,
+      current_storage_partition_name_, true /*in_memory */);
   current_storage_partition_ = content::BrowserContext::GetStoragePartition(
       browser_context_, storage_partition_config, true);
   if (on_create_new_storage_partition_) {
diff --git a/chrome/browser/ash/login/users/chrome_user_manager_impl.cc b/chrome/browser/ash/login/users/chrome_user_manager_impl.cc
index 97f1f7b..f719102 100644
--- a/chrome/browser/ash/login/users/chrome_user_manager_impl.cc
+++ b/chrome/browser/ash/login/users/chrome_user_manager_impl.cc
@@ -34,6 +34,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
 #include "chrome/browser/ash/app_mode/kiosk_app_manager.h"
+#include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/ash/login/demo_mode/demo_app_launcher.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service.h"
 #include "chrome/browser/ash/login/enterprise_user_session_metrics.h"
@@ -472,6 +473,10 @@
   if (connector->IsActiveDirectoryManaged())
     return user_manager::UserList();
 
+  // Multiprofile mode is not allowed when Lacros is enabled.
+  if (crosapi::browser_util::IsLacrosEnabled())
+    return user_manager::UserList();
+
   user_manager::UserList result;
   const user_manager::UserList& users = GetUsers();
   for (user_manager::UserList::const_iterator it = users.begin();
diff --git a/chrome/browser/chromeos/scanning/OWNERS b/chrome/browser/ash/scanning/OWNERS
similarity index 100%
rename from chrome/browser/chromeos/scanning/OWNERS
rename to chrome/browser/ash/scanning/OWNERS
diff --git a/chrome/browser/chromeos/scanning/fake_lorgnette_scanner_manager.cc b/chrome/browser/ash/scanning/fake_lorgnette_scanner_manager.cc
similarity index 97%
rename from chrome/browser/chromeos/scanning/fake_lorgnette_scanner_manager.cc
rename to chrome/browser/ash/scanning/fake_lorgnette_scanner_manager.cc
index a048a96..9bc5d8b 100644
--- a/chrome/browser/chromeos/scanning/fake_lorgnette_scanner_manager.cc
+++ b/chrome/browser/ash/scanning/fake_lorgnette_scanner_manager.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/fake_lorgnette_scanner_manager.h"
+#include "chrome/browser/ash/scanning/fake_lorgnette_scanner_manager.h"
 
 #include <utility>
 
diff --git a/chrome/browser/chromeos/scanning/fake_lorgnette_scanner_manager.h b/chrome/browser/ash/scanning/fake_lorgnette_scanner_manager.h
similarity index 86%
rename from chrome/browser/chromeos/scanning/fake_lorgnette_scanner_manager.h
rename to chrome/browser/ash/scanning/fake_lorgnette_scanner_manager.h
index b277875..ae38510 100644
--- a/chrome/browser/chromeos/scanning/fake_lorgnette_scanner_manager.h
+++ b/chrome/browser/ash/scanning/fake_lorgnette_scanner_manager.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_SCANNING_FAKE_LORGNETTE_SCANNER_MANAGER_H_
-#define CHROME_BROWSER_CHROMEOS_SCANNING_FAKE_LORGNETTE_SCANNER_MANAGER_H_
+#ifndef CHROME_BROWSER_ASH_SCANNING_FAKE_LORGNETTE_SCANNER_MANAGER_H_
+#define CHROME_BROWSER_ASH_SCANNING_FAKE_LORGNETTE_SCANNER_MANAGER_H_
 
 #include <string>
 #include <vector>
 
 #include "base/optional.h"
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager.h"
 #include "chromeos/dbus/lorgnette/lorgnette_service.pb.h"
 #include "chromeos/dbus/lorgnette_manager/lorgnette_manager_client.h"
 
@@ -56,4 +56,4 @@
 
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_SCANNING_FAKE_LORGNETTE_SCANNER_MANAGER_H_
+#endif  // CHROME_BROWSER_ASH_SCANNING_FAKE_LORGNETTE_SCANNER_MANAGER_H_
diff --git a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager.cc b/chrome/browser/ash/scanning/lorgnette_scanner_manager.cc
similarity index 97%
rename from chrome/browser/chromeos/scanning/lorgnette_scanner_manager.cc
rename to chrome/browser/ash/scanning/lorgnette_scanner_manager.cc
index af9bcb6..48b5e8b 100644
--- a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager.cc
+++ b/chrome/browser/ash/scanning/lorgnette_scanner_manager.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager.h"
 
 #include <array>
 #include <memory>
@@ -17,8 +17,8 @@
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/strings/stringprintf.h"
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager_util.h"
-#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager_util.h"
+#include "chrome/browser/ash/scanning/zeroconf_scanner_detector.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/lorgnette/lorgnette_service.pb.h"
 #include "chromeos/dbus/lorgnette_manager/lorgnette_manager_client.h"
diff --git a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager.h b/chrome/browser/ash/scanning/lorgnette_scanner_manager.h
similarity index 93%
rename from chrome/browser/chromeos/scanning/lorgnette_scanner_manager.h
rename to chrome/browser/ash/scanning/lorgnette_scanner_manager.h
index 861b253c..dc1c3cc 100644
--- a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager.h
+++ b/chrome/browser/ash/scanning/lorgnette_scanner_manager.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_SCANNING_LORGNETTE_SCANNER_MANAGER_H_
-#define CHROME_BROWSER_CHROMEOS_SCANNING_LORGNETTE_SCANNER_MANAGER_H_
+#ifndef CHROME_BROWSER_ASH_SCANNING_LORGNETTE_SCANNER_MANAGER_H_
+#define CHROME_BROWSER_ASH_SCANNING_LORGNETTE_SCANNER_MANAGER_H_
 
 #include <cstdint>
 #include <memory>
@@ -71,4 +71,4 @@
 
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_SCANNING_LORGNETTE_SCANNER_MANAGER_H_
+#endif  // CHROME_BROWSER_ASH_SCANNING_LORGNETTE_SCANNER_MANAGER_H_
diff --git a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager_factory.cc b/chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.cc
similarity index 89%
rename from chrome/browser/chromeos/scanning/lorgnette_scanner_manager_factory.cc
rename to chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.cc
index 280f479..465aa75 100644
--- a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager_factory.cc
+++ b/chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.cc
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager_factory.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.h"
 
 #include "base/memory/singleton.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager.h"
-#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager.h"
+#include "chrome/browser/ash/scanning/zeroconf_scanner_detector.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "content/public/browser/browser_context.h"
diff --git a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager_factory.h b/chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.h
similarity index 84%
rename from chrome/browser/chromeos/scanning/lorgnette_scanner_manager_factory.h
rename to chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.h
index 003c581c..583c460 100644
--- a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager_factory.h
+++ b/chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_SCANNING_LORGNETTE_SCANNER_MANAGER_FACTORY_H_
-#define CHROME_BROWSER_CHROMEOS_SCANNING_LORGNETTE_SCANNER_MANAGER_FACTORY_H_
+#ifndef CHROME_BROWSER_ASH_SCANNING_LORGNETTE_SCANNER_MANAGER_FACTORY_H_
+#define CHROME_BROWSER_ASH_SCANNING_LORGNETTE_SCANNER_MANAGER_FACTORY_H_
 
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 
@@ -48,4 +48,4 @@
 
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_SCANNING_LORGNETTE_SCANNER_MANAGER_FACTORY_H_
+#endif  // CHROME_BROWSER_ASH_SCANNING_LORGNETTE_SCANNER_MANAGER_FACTORY_H_
diff --git a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager_unittest.cc b/chrome/browser/ash/scanning/lorgnette_scanner_manager_unittest.cc
similarity index 98%
rename from chrome/browser/chromeos/scanning/lorgnette_scanner_manager_unittest.cc
rename to chrome/browser/ash/scanning/lorgnette_scanner_manager_unittest.cc
index b599683..9450baf 100644
--- a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager_unittest.cc
+++ b/chrome/browser/ash/scanning/lorgnette_scanner_manager_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager.h"
 
 #include <cstdint>
 #include <memory>
@@ -17,8 +17,8 @@
 #include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
-#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector.h"
-#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.h"
+#include "chrome/browser/ash/scanning/zeroconf_scanner_detector.h"
+#include "chrome/browser/ash/scanning/zeroconf_scanner_detector_utils.h"
 #include "chrome/browser/local_discovery/service_discovery_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/lorgnette/lorgnette_service.pb.h"
diff --git a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager_util.cc b/chrome/browser/ash/scanning/lorgnette_scanner_manager_util.cc
similarity index 91%
rename from chrome/browser/chromeos/scanning/lorgnette_scanner_manager_util.cc
rename to chrome/browser/ash/scanning/lorgnette_scanner_manager_util.cc
index e1a72c5..759012c07 100644
--- a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager_util.cc
+++ b/chrome/browser/ash/scanning/lorgnette_scanner_manager_util.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager_util.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager_util.h"
 
 #include "third_party/re2/src/re2/re2.h"
 
diff --git a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager_util.h b/chrome/browser/ash/scanning/lorgnette_scanner_manager_util.h
similarity index 74%
rename from chrome/browser/chromeos/scanning/lorgnette_scanner_manager_util.h
rename to chrome/browser/ash/scanning/lorgnette_scanner_manager_util.h
index 56b7c3c..f2252ff 100644
--- a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager_util.h
+++ b/chrome/browser/ash/scanning/lorgnette_scanner_manager_util.h
@@ -4,8 +4,8 @@
 
 // Utility functions for the LorgnetteScannerManager.
 
-#ifndef CHROME_BROWSER_CHROMEOS_SCANNING_LORGNETTE_SCANNER_MANAGER_UTIL_H_
-#define CHROME_BROWSER_CHROMEOS_SCANNING_LORGNETTE_SCANNER_MANAGER_UTIL_H_
+#ifndef CHROME_BROWSER_ASH_SCANNING_LORGNETTE_SCANNER_MANAGER_UTIL_H_
+#define CHROME_BROWSER_ASH_SCANNING_LORGNETTE_SCANNER_MANAGER_UTIL_H_
 
 #include <string>
 
@@ -22,4 +22,4 @@
 
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_SCANNING_LORGNETTE_SCANNER_MANAGER_UTIL_H_
+#endif  // CHROME_BROWSER_ASH_SCANNING_LORGNETTE_SCANNER_MANAGER_UTIL_H_
diff --git a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager_util_unittest.cc b/chrome/browser/ash/scanning/lorgnette_scanner_manager_util_unittest.cc
similarity index 95%
rename from chrome/browser/chromeos/scanning/lorgnette_scanner_manager_util_unittest.cc
rename to chrome/browser/ash/scanning/lorgnette_scanner_manager_util_unittest.cc
index 8e67914..fe346a7 100644
--- a/chrome/browser/chromeos/scanning/lorgnette_scanner_manager_util_unittest.cc
+++ b/chrome/browser/ash/scanning/lorgnette_scanner_manager_util_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager_util.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager_util.h"
 
 #include <string>
 
diff --git a/chrome/browser/chromeos/scanning/scan_service.cc b/chrome/browser/ash/scanning/scan_service.cc
similarity index 98%
rename from chrome/browser/chromeos/scanning/scan_service.cc
rename to chrome/browser/ash/scanning/scan_service.cc
index 50bfd0e..0916197 100644
--- a/chrome/browser/chromeos/scanning/scan_service.cc
+++ b/chrome/browser/ash/scanning/scan_service.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/scan_service.h"
+#include "chrome/browser/ash/scanning/scan_service.h"
 
 #include <cstdint>
 #include <utility>
@@ -21,8 +21,8 @@
 #include "base/task/thread_pool.h"
 #include "base/task_runner_util.h"
 #include "base/time/time.h"
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager.h"
-#include "chrome/browser/chromeos/scanning/scanning_type_converters.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager.h"
+#include "chrome/browser/ash/scanning/scanning_type_converters.h"
 #include "chromeos/components/scanning/scanning_uma.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkData.h"
diff --git a/chrome/browser/chromeos/scanning/scan_service.h b/chrome/browser/ash/scanning/scan_service.h
similarity index 97%
rename from chrome/browser/chromeos/scanning/scan_service.h
rename to chrome/browser/ash/scanning/scan_service.h
index aac5425..785bd5b 100644
--- a/chrome/browser/chromeos/scanning/scan_service.h
+++ b/chrome/browser/ash/scanning/scan_service.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_SCANNING_SCAN_SERVICE_H_
-#define CHROME_BROWSER_CHROMEOS_SCANNING_SCAN_SERVICE_H_
+#ifndef CHROME_BROWSER_ASH_SCANNING_SCAN_SERVICE_H_
+#define CHROME_BROWSER_ASH_SCANNING_SCAN_SERVICE_H_
 
 #include <cstdint>
 #include <string>
@@ -169,4 +169,4 @@
 
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_SCANNING_SCAN_SERVICE_H_
+#endif  // CHROME_BROWSER_ASH_SCANNING_SCAN_SERVICE_H_
diff --git a/chrome/browser/chromeos/scanning/scan_service_factory.cc b/chrome/browser/ash/scanning/scan_service_factory.cc
similarity index 92%
rename from chrome/browser/chromeos/scanning/scan_service_factory.cc
rename to chrome/browser/ash/scanning/scan_service_factory.cc
index 39d48dc..33a125f6 100644
--- a/chrome/browser/chromeos/scanning/scan_service_factory.cc
+++ b/chrome/browser/ash/scanning/scan_service_factory.cc
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/scan_service_factory.h"
+#include "chrome/browser/ash/scanning/scan_service_factory.h"
 
 #include "base/memory/singleton.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.h"
+#include "chrome/browser/ash/scanning/scan_service.h"
 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
 #include "chrome/browser/chromeos/file_manager/path_util.h"
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager_factory.h"
-#include "chrome/browser/chromeos/scanning/scan_service.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
diff --git a/chrome/browser/chromeos/scanning/scan_service_factory.h b/chrome/browser/ash/scanning/scan_service_factory.h
similarity index 87%
rename from chrome/browser/chromeos/scanning/scan_service_factory.h
rename to chrome/browser/ash/scanning/scan_service_factory.h
index 1b859b4..1a583ed3 100644
--- a/chrome/browser/chromeos/scanning/scan_service_factory.h
+++ b/chrome/browser/ash/scanning/scan_service_factory.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_SCANNING_SCAN_SERVICE_FACTORY_H_
-#define CHROME_BROWSER_CHROMEOS_SCANNING_SCAN_SERVICE_FACTORY_H_
+#ifndef CHROME_BROWSER_ASH_SCANNING_SCAN_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_ASH_SCANNING_SCAN_SERVICE_FACTORY_H_
 
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 
@@ -47,4 +47,4 @@
 
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_SCANNING_SCAN_SERVICE_FACTORY_H_
+#endif  // CHROME_BROWSER_ASH_SCANNING_SCAN_SERVICE_FACTORY_H_
diff --git a/chrome/browser/chromeos/scanning/scan_service_factory_unittest.cc b/chrome/browser/ash/scanning/scan_service_factory_unittest.cc
similarity index 91%
rename from chrome/browser/chromeos/scanning/scan_service_factory_unittest.cc
rename to chrome/browser/ash/scanning/scan_service_factory_unittest.cc
index b7cd92bb..c998beb 100644
--- a/chrome/browser/chromeos/scanning/scan_service_factory_unittest.cc
+++ b/chrome/browser/ash/scanning/scan_service_factory_unittest.cc
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/scan_service_factory.h"
+#include "chrome/browser/ash/scanning/scan_service_factory.h"
 
 #include <memory>
 #include <string>
 
 #include "base/bind.h"
 #include "base/files/file_path.h"
-#include "chrome/browser/chromeos/scanning/fake_lorgnette_scanner_manager.h"
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager_factory.h"
-#include "chrome/browser/chromeos/scanning/scan_service.h"
+#include "chrome/browser/ash/scanning/fake_lorgnette_scanner_manager.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.h"
+#include "chrome/browser/ash/scanning/scan_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/test/base/testing_profile.h"
diff --git a/chrome/browser/chromeos/scanning/scan_service_unittest.cc b/chrome/browser/ash/scanning/scan_service_unittest.cc
similarity index 99%
rename from chrome/browser/chromeos/scanning/scan_service_unittest.cc
rename to chrome/browser/ash/scanning/scan_service_unittest.cc
index e97b0bf..6a54270e 100644
--- a/chrome/browser/chromeos/scanning/scan_service_unittest.cc
+++ b/chrome/browser/ash/scanning/scan_service_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/scan_service.h"
+#include "chrome/browser/ash/scanning/scan_service.h"
 
 #include <cstdint>
 #include <map>
@@ -20,7 +20,7 @@
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "base/unguessable_token.h"
-#include "chrome/browser/chromeos/scanning/fake_lorgnette_scanner_manager.h"
+#include "chrome/browser/ash/scanning/fake_lorgnette_scanner_manager.h"
 #include "chromeos/components/scanning/mojom/scanning.mojom-test-utils.h"
 #include "chromeos/components/scanning/mojom/scanning.mojom.h"
 #include "chromeos/dbus/lorgnette/lorgnette_service.pb.h"
diff --git a/chrome/browser/chromeos/scanning/scanner_detector.h b/chrome/browser/ash/scanning/scanner_detector.h
similarity index 87%
rename from chrome/browser/chromeos/scanning/scanner_detector.h
rename to chrome/browser/ash/scanning/scanner_detector.h
index ac770e1..1a7b2945 100644
--- a/chrome/browser/chromeos/scanning/scanner_detector.h
+++ b/chrome/browser/ash/scanning/scanner_detector.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_SCANNING_SCANNER_DETECTOR_H_
-#define CHROME_BROWSER_CHROMEOS_SCANNING_SCANNER_DETECTOR_H_
+#ifndef CHROME_BROWSER_ASH_SCANNING_SCANNER_DETECTOR_H_
+#define CHROME_BROWSER_ASH_SCANNING_SCANNER_DETECTOR_H_
 
 #include <vector>
 
@@ -42,4 +42,4 @@
 
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_SCANNING_SCANNER_DETECTOR_H_
+#endif  // CHROME_BROWSER_ASH_SCANNING_SCANNER_DETECTOR_H_
diff --git a/chrome/browser/chromeos/scanning/scanning_paths_provider_impl.cc b/chrome/browser/ash/scanning/scanning_paths_provider_impl.cc
similarity index 95%
rename from chrome/browser/chromeos/scanning/scanning_paths_provider_impl.cc
rename to chrome/browser/ash/scanning/scanning_paths_provider_impl.cc
index cb491531..288e288 100644
--- a/chrome/browser/chromeos/scanning/scanning_paths_provider_impl.cc
+++ b/chrome/browser/ash/scanning/scanning_paths_provider_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/scanning_paths_provider_impl.h"
+#include "chrome/browser/ash/scanning/scanning_paths_provider_impl.h"
 
 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
 #include "chrome/browser/chromeos/file_manager/path_util.h"
diff --git a/chrome/browser/chromeos/scanning/scanning_paths_provider_impl.h b/chrome/browser/ash/scanning/scanning_paths_provider_impl.h
similarity index 80%
rename from chrome/browser/chromeos/scanning/scanning_paths_provider_impl.h
rename to chrome/browser/ash/scanning/scanning_paths_provider_impl.h
index 570cf28..53b2c97 100644
--- a/chrome/browser/chromeos/scanning/scanning_paths_provider_impl.h
+++ b/chrome/browser/ash/scanning/scanning_paths_provider_impl.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_SCANNING_SCANNING_PATHS_PROVIDER_IMPL_H_
-#define CHROME_BROWSER_CHROMEOS_SCANNING_SCANNING_PATHS_PROVIDER_IMPL_H_
+#ifndef CHROME_BROWSER_ASH_SCANNING_SCANNING_PATHS_PROVIDER_IMPL_H_
+#define CHROME_BROWSER_ASH_SCANNING_SCANNING_PATHS_PROVIDER_IMPL_H_
 
 #include <string>
 
@@ -33,4 +33,4 @@
 
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_SCANNING_SCANNING_PATHS_PROVIDER_IMPL_H_
+#endif  // CHROME_BROWSER_ASH_SCANNING_SCANNING_PATHS_PROVIDER_IMPL_H_
diff --git a/chrome/browser/chromeos/scanning/scanning_paths_provider_impl_unittest.cc b/chrome/browser/ash/scanning/scanning_paths_provider_impl_unittest.cc
similarity index 98%
rename from chrome/browser/chromeos/scanning/scanning_paths_provider_impl_unittest.cc
rename to chrome/browser/ash/scanning/scanning_paths_provider_impl_unittest.cc
index 0e2cb3c3..0620f80 100644
--- a/chrome/browser/chromeos/scanning/scanning_paths_provider_impl_unittest.cc
+++ b/chrome/browser/ash/scanning/scanning_paths_provider_impl_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/scanning_paths_provider_impl.h"
+#include "chrome/browser/ash/scanning/scanning_paths_provider_impl.h"
 
 #include <memory>
 
diff --git a/chrome/browser/chromeos/scanning/scanning_type_converters.cc b/chrome/browser/ash/scanning/scanning_type_converters.cc
similarity index 98%
rename from chrome/browser/chromeos/scanning/scanning_type_converters.cc
rename to chrome/browser/ash/scanning/scanning_type_converters.cc
index fe8a4c8..d3d6ba5 100644
--- a/chrome/browser/chromeos/scanning/scanning_type_converters.cc
+++ b/chrome/browser/ash/scanning/scanning_type_converters.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/scanning_type_converters.h"
+#include "chrome/browser/ash/scanning/scanning_type_converters.h"
 
 #include "base/notreached.h"
 #include "chromeos/dbus/lorgnette/lorgnette_service.pb.h"
diff --git a/chrome/browser/chromeos/scanning/scanning_type_converters.h b/chrome/browser/ash/scanning/scanning_type_converters.h
similarity index 81%
rename from chrome/browser/chromeos/scanning/scanning_type_converters.h
rename to chrome/browser/ash/scanning/scanning_type_converters.h
index faaaad0..5a00329e 100644
--- a/chrome/browser/chromeos/scanning/scanning_type_converters.h
+++ b/chrome/browser/ash/scanning/scanning_type_converters.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_SCANNING_SCANNING_TYPE_CONVERTERS_H_
-#define CHROME_BROWSER_CHROMEOS_SCANNING_SCANNING_TYPE_CONVERTERS_H_
+#ifndef CHROME_BROWSER_ASH_SCANNING_SCANNING_TYPE_CONVERTERS_H_
+#define CHROME_BROWSER_ASH_SCANNING_SCANNING_TYPE_CONVERTERS_H_
 
 #include "chromeos/components/scanning/mojom/scanning.mojom.h"
 #include "mojo/public/cpp/bindings/type_converter.h"
@@ -31,4 +31,4 @@
 
 }  // namespace mojo
 
-#endif  // CHROME_BROWSER_CHROMEOS_SCANNING_SCANNING_TYPE_CONVERTERS_H_
+#endif  // CHROME_BROWSER_ASH_SCANNING_SCANNING_TYPE_CONVERTERS_H_
diff --git a/chrome/browser/chromeos/scanning/scanning_type_converters_unittest.cc b/chrome/browser/ash/scanning/scanning_type_converters_unittest.cc
similarity index 98%
rename from chrome/browser/chromeos/scanning/scanning_type_converters_unittest.cc
rename to chrome/browser/ash/scanning/scanning_type_converters_unittest.cc
index d369db5..83d66c8e 100644
--- a/chrome/browser/chromeos/scanning/scanning_type_converters_unittest.cc
+++ b/chrome/browser/ash/scanning/scanning_type_converters_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/scanning_type_converters.h"
+#include "chrome/browser/ash/scanning/scanning_type_converters.h"
 
 #include "chromeos/components/scanning/mojom/scanning.mojom.h"
 #include "chromeos/dbus/lorgnette/lorgnette_service.pb.h"
diff --git a/chrome/browser/chromeos/scanning/scanning_util.cc b/chrome/browser/ash/scanning/scanning_util.cc
similarity index 100%
rename from chrome/browser/chromeos/scanning/scanning_util.cc
rename to chrome/browser/ash/scanning/scanning_util.cc
diff --git a/chrome/browser/chromeos/scanning/scanning_util.h b/chrome/browser/ash/scanning/scanning_util.h
similarity index 83%
rename from chrome/browser/chromeos/scanning/scanning_util.h
rename to chrome/browser/ash/scanning/scanning_util.h
index deb2439..5c7798c 100644
--- a/chrome/browser/chromeos/scanning/scanning_util.h
+++ b/chrome/browser/ash/scanning/scanning_util.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_SCANNING_SCANNING_UTIL_H_
-#define CHROME_BROWSER_CHROMEOS_SCANNING_SCANNING_UTIL_H_
+#ifndef CHROME_BROWSER_ASH_SCANNING_SCANNING_UTIL_H_
+#define CHROME_BROWSER_ASH_SCANNING_SCANNING_UTIL_H_
 
 namespace base {
 class FilePath;
@@ -32,4 +32,4 @@
 }  // namespace scanning
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_SCANNING_SCANNING_UTIL_H_
+#endif  // CHROME_BROWSER_ASH_SCANNING_SCANNING_UTIL_H_
diff --git a/chrome/browser/chromeos/scanning/scanning_util_unittest.cc b/chrome/browser/ash/scanning/scanning_util_unittest.cc
similarity index 98%
rename from chrome/browser/chromeos/scanning/scanning_util_unittest.cc
rename to chrome/browser/ash/scanning/scanning_util_unittest.cc
index 03c61372..feca5df 100644
--- a/chrome/browser/chromeos/scanning/scanning_util_unittest.cc
+++ b/chrome/browser/ash/scanning/scanning_util_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/scanning_util.h"
+#include "chrome/browser/ash/scanning/scanning_util.h"
 
 #include <memory>
 
diff --git a/chrome/browser/chromeos/scanning/zeroconf_scanner_detector.cc b/chrome/browser/ash/scanning/zeroconf_scanner_detector.cc
similarity index 98%
rename from chrome/browser/chromeos/scanning/zeroconf_scanner_detector.cc
rename to chrome/browser/ash/scanning/zeroconf_scanner_detector.cc
index 9c8621e..9c4983d2 100644
--- a/chrome/browser/chromeos/scanning/zeroconf_scanner_detector.cc
+++ b/chrome/browser/ash/scanning/zeroconf_scanner_detector.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector.h"
+#include "chrome/browser/ash/scanning/zeroconf_scanner_detector.h"
 
 #include <array>
 #include <string>
@@ -17,7 +17,7 @@
 #include "base/strings/string_piece_forward.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
-#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.h"
+#include "chrome/browser/ash/scanning/zeroconf_scanner_detector_utils.h"
 #include "chrome/browser/local_discovery/service_discovery_shared_client.h"
 #include "chromeos/scanning/scanner.h"
 
diff --git a/chrome/browser/chromeos/scanning/zeroconf_scanner_detector.h b/chrome/browser/ash/scanning/zeroconf_scanner_detector.h
similarity index 82%
rename from chrome/browser/chromeos/scanning/zeroconf_scanner_detector.h
rename to chrome/browser/ash/scanning/zeroconf_scanner_detector.h
index 0d4d8b2..2078b30 100644
--- a/chrome/browser/chromeos/scanning/zeroconf_scanner_detector.h
+++ b/chrome/browser/ash/scanning/zeroconf_scanner_detector.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_SCANNING_ZEROCONF_SCANNER_DETECTOR_H_
-#define CHROME_BROWSER_CHROMEOS_SCANNING_ZEROCONF_SCANNER_DETECTOR_H_
+#ifndef CHROME_BROWSER_ASH_SCANNING_ZEROCONF_SCANNER_DETECTOR_H_
+#define CHROME_BROWSER_ASH_SCANNING_ZEROCONF_SCANNER_DETECTOR_H_
 
 #include <memory>
 #include <string>
 
 #include "base/containers/flat_map.h"
-#include "chrome/browser/chromeos/scanning/scanner_detector.h"
+#include "chrome/browser/ash/scanning/scanner_detector.h"
 #include "chrome/browser/local_discovery/service_discovery_device_lister.h"
 
 namespace chromeos {
@@ -40,4 +40,4 @@
 
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_SCANNING_ZEROCONF_SCANNER_DETECTOR_H_
+#endif  // CHROME_BROWSER_ASH_SCANNING_ZEROCONF_SCANNER_DETECTOR_H_
diff --git a/chrome/browser/chromeos/scanning/zeroconf_scanner_detector_unittest.cc b/chrome/browser/ash/scanning/zeroconf_scanner_detector_unittest.cc
similarity index 98%
rename from chrome/browser/chromeos/scanning/zeroconf_scanner_detector_unittest.cc
rename to chrome/browser/ash/scanning/zeroconf_scanner_detector_unittest.cc
index 0d9ebf0..ef1bcaf 100644
--- a/chrome/browser/chromeos/scanning/zeroconf_scanner_detector_unittest.cc
+++ b/chrome/browser/ash/scanning/zeroconf_scanner_detector_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector.h"
+#include "chrome/browser/ash/scanning/zeroconf_scanner_detector.h"
 
 #include <algorithm>
 #include <functional>
@@ -16,7 +16,7 @@
 #include "base/bind.h"
 #include "base/strings/strcat.h"
 #include "base/test/task_environment.h"
-#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.h"
+#include "chrome/browser/ash/scanning/zeroconf_scanner_detector_utils.h"
 #include "chrome/browser/local_discovery/fake_service_discovery_device_lister.h"
 #include "chrome/browser/local_discovery/service_discovery_device_lister.h"
 #include "chromeos/scanning/scanner.h"
diff --git a/chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.cc b/chrome/browser/ash/scanning/zeroconf_scanner_detector_utils.cc
similarity index 94%
rename from chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.cc
rename to chrome/browser/ash/scanning/zeroconf_scanner_detector_utils.cc
index b6a7e78..5cf5321 100644
--- a/chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.cc
+++ b/chrome/browser/ash/scanning/zeroconf_scanner_detector_utils.cc
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.h"
+#include "chrome/browser/ash/scanning/zeroconf_scanner_detector_utils.h"
 
 #include <string>
 
 #include "base/logging.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
-#include "chrome/browser/chromeos/scanning/zeroconf_scanner_detector.h"
+#include "chrome/browser/ash/scanning/zeroconf_scanner_detector.h"
 #include "url/gurl.h"
 
 namespace chromeos {
diff --git a/chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.h b/chrome/browser/ash/scanning/zeroconf_scanner_detector_utils.h
similarity index 82%
rename from chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.h
rename to chrome/browser/ash/scanning/zeroconf_scanner_detector_utils.h
index d48dd7b..eb7dfd4 100644
--- a/chrome/browser/chromeos/scanning/zeroconf_scanner_detector_utils.h
+++ b/chrome/browser/ash/scanning/zeroconf_scanner_detector_utils.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_SCANNING_ZEROCONF_SCANNER_DETECTOR_UTILS_H_
-#define CHROME_BROWSER_CHROMEOS_SCANNING_ZEROCONF_SCANNER_DETECTOR_UTILS_H_
+#ifndef CHROME_BROWSER_ASH_SCANNING_ZEROCONF_SCANNER_DETECTOR_UTILS_H_
+#define CHROME_BROWSER_ASH_SCANNING_ZEROCONF_SCANNER_DETECTOR_UTILS_H_
 
 #include <string>
 
@@ -31,4 +31,4 @@
 
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_CHROMEOS_SCANNING_ZEROCONF_SCANNER_DETECTOR_UTILS_H_
+#endif  // CHROME_BROWSER_ASH_SCANNING_ZEROCONF_SCANNER_DETECTOR_UTILS_H_
diff --git a/chrome/browser/ash/web_applications/chrome_help_app_ui_delegate.cc b/chrome/browser/ash/web_applications/chrome_help_app_ui_delegate.cc
index 292eb10..b85c661d 100644
--- a/chrome/browser/ash/web_applications/chrome_help_app_ui_delegate.cc
+++ b/chrome/browser/ash/web_applications/chrome_help_app_ui_delegate.cc
@@ -85,6 +85,11 @@
 
   // Add any features that have been enabled.
   source->AddBoolean("HelpAppReleaseNotes", true);
+  source->AddBoolean("HelpAppLauncherSearch",
+                     base::FeatureList::IsEnabled(
+                         chromeos::features::kHelpAppLauncherSearch) &&
+                         base::FeatureList::IsEnabled(
+                             chromeos::features::kEnableLocalSearchService));
   source->AddBoolean(
       "HelpAppSearchServiceIntegration",
       base::FeatureList::IsEnabled(
diff --git a/chrome/browser/browsing_data/counters/site_data_counting_helper_unittest.cc b/chrome/browser/browsing_data/counters/site_data_counting_helper_unittest.cc
index a7d245e..a6b4baf 100644
--- a/chrome/browser/browsing_data/counters/site_data_counting_helper_unittest.cc
+++ b/chrome/browser/browsing_data/counters/site_data_counting_helper_unittest.cc
@@ -29,6 +29,11 @@
 
   void SetUp() override {
     profile_.reset(new TestingProfile());
+    // Let the storage system finish setting up, to avoid test flakiness caused
+    // by the quota storage system shutting down at test end, while still being
+    // set up. TODO(crbug.com/1182630) Remove when crbug.com/1182630 is fixed.
+    content::BrowserContext::GetDefaultStoragePartition(profile());
+    task_environment_.RunUntilIdle();
   }
 
   void TearDown() override {
diff --git a/chrome/browser/certificate_manager_model.cc b/chrome/browser/certificate_manager_model.cc
index 4023e59..289f2bf3 100644
--- a/chrome/browser/certificate_manager_model.cc
+++ b/chrome/browser/certificate_manager_model.cc
@@ -56,7 +56,7 @@
 //                  \--------------------------------------v
 //                                CertificateManagerModel::GetCertDBOnIOThread
 //                                                         |
-//                                     GetNSSCertDatabaseForResourceContext
+//                                               NssCertDatabaseGetter
 //                                                         |
 //                               CertificateManagerModel::DidGetCertDBOnIOThread
 //                                                         |
@@ -511,10 +511,10 @@
 #endif
 
   content::GetIOThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(&CertificateManagerModel::GetCertDBOnIOThread,
-                     std::move(params), browser_context->GetResourceContext(),
-                     observer, std::move(callback)));
+      FROM_HERE, base::BindOnce(&CertificateManagerModel::GetCertDBOnIOThread,
+                                std::move(params),
+                                CreateNSSCertDatabaseGetter(browser_context),
+                                observer, std::move(callback)));
 }
 
 CertificateManagerModel::CertificateManagerModel(
@@ -712,7 +712,7 @@
 // static
 void CertificateManagerModel::GetCertDBOnIOThread(
     std::unique_ptr<Params> params,
-    content::ResourceContext* resource_context,
+    NssCertDatabaseGetter database_getter,
     CertificateManagerModel::Observer* observer,
     CreationCallback callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
@@ -721,8 +721,8 @@
       base::BindOnce(&CertificateManagerModel::DidGetCertDBOnIOThread,
                      std::move(params), observer, std::move(callback)));
 
-  net::NSSCertDatabase* cert_db = GetNSSCertDatabaseForResourceContext(
-      resource_context, did_get_cert_db_callback);
+  net::NSSCertDatabase* cert_db =
+      std::move(database_getter).Run(did_get_cert_db_callback);
   // If the NSS database was already available, |cert_db| is non-null and
   // |did_get_cert_db_callback| has not been called. Call it explicitly.
   if (cert_db)
diff --git a/chrome/browser/certificate_manager_model.h b/chrome/browser/certificate_manager_model.h
index 695f077..cddce32 100644
--- a/chrome/browser/certificate_manager_model.h
+++ b/chrome/browser/certificate_manager_model.h
@@ -14,13 +14,13 @@
 #include "base/memory/ref_counted.h"
 #include "base/strings/string16.h"
 #include "build/chromeos_buildflags.h"
+#include "chrome/browser/net/nss_context.h"
 #include "net/cert/nss_cert_database.h"
 #include "net/cert/scoped_nss_types.h"
 #include "net/ssl/client_cert_identity.h"
 
 namespace content {
 class BrowserContext;
-class ResourceContext;
 }  // namespace content
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -267,7 +267,7 @@
       CreationCallback callback,
       net::NSSCertDatabase* cert_db);
   static void GetCertDBOnIOThread(std::unique_ptr<Params> params,
-                                  content::ResourceContext* resource_context,
+                                  NssCertDatabaseGetter database_getter,
                                   CertificateManagerModel::Observer* observer,
                                   CreationCallback callback);
 
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 6bbcaaf..2d38aab 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -486,6 +486,7 @@
 #include "chrome/browser/search/instant_service.h"
 #include "chrome/browser/search/instant_service_factory.h"
 #include "chrome/browser/serial/chrome_serial_delegate.h"
+#include "chrome/browser/task_manager/task_manager_interface.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
@@ -1445,11 +1446,11 @@
   // Default to the browser-wide storage partition and override based on |site|
   // below.
   content::StoragePartitionConfig storage_partition_config =
-      content::StoragePartitionConfig::CreateDefault();
+      content::StoragePartitionConfig::CreateDefault(browser_context);
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
   if (extensions::WebViewGuest::GetGuestPartitionConfigForSite(
-          site, &storage_partition_config)) {
+          browser_context, site, &storage_partition_config)) {
     return storage_partition_config;
   }
 
@@ -5400,6 +5401,8 @@
 }
 
 void ChromeContentBrowserClient::OnNetworkServiceDataUseUpdate(
+    int process_id,
+    int routing_id,
     int32_t network_traffic_annotation_id_hash,
     int64_t recv_bytes,
     int64_t sent_bytes) {
@@ -5408,6 +5411,11 @@
         ->ReportNetworkServiceDataUse(network_traffic_annotation_id_hash,
                                       recv_bytes, sent_bytes);
   }
+#if !defined(OS_ANDROID)
+  task_manager::TaskManagerInterface::GetTaskManager()
+      ->UpdateAccumulatedStatsNetworkForRoute(process_id, routing_id,
+                                              recv_bytes, sent_bytes);
+#endif
 }
 
 base::FilePath
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 36674a8f..96c6ae0 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -587,7 +587,9 @@
       network::OriginPolicyState error_reason,
       content::NavigationHandle* handle) override;
   bool CanAcceptUntrustedExchangesIfNeeded() override;
-  void OnNetworkServiceDataUseUpdate(int32_t network_traffic_annotation_id_hash,
+  void OnNetworkServiceDataUseUpdate(int process_id,
+                                     int routing_id,
+                                     int32_t network_traffic_annotation_id_hash,
                                      int64_t recv_bytes,
                                      int64_t sent_bytes) override;
   base::FilePath GetSandboxedStorageServiceDataDirectory() override;
diff --git a/chrome/browser/chrome_notification_types.h b/chrome/browser/chrome_notification_types.h
index 7956d18..858b7d9 100644
--- a/chrome/browser/chrome_notification_types.h
+++ b/chrome/browser/chrome_notification_types.h
@@ -55,11 +55,6 @@
   // TODO(https://crbug.com/1174777): Remove.
   NOTIFICATION_BROWSER_CLOSE_CANCELLED,
 
-  // The user has changed the browser theme. The source is a
-  // Source<ThemeService>. There are no details.
-  // TODO(https://crbug.com/1174780): Remove.
-  NOTIFICATION_BROWSER_THEME_CHANGED,
-
   // Application-wide ----------------------------------------------------------
 
   // This message is sent when the application is terminating (the last
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index fe338c4e..3ec2a45 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -314,6 +314,7 @@
     "//components/reporting/client:report_queue_provider",
     "//components/reporting/util:status",
     "//components/reporting/util:task_runner_context",
+    "//components/reporting/util:test_callbacks_support",
     "//components/rlz",
     "//components/safe_browsing/core:csd_proto",
     "//components/safe_browsing/core/db:metadata_proto",
@@ -1091,6 +1092,27 @@
     "../ash/profiles/profile_helper.cc",
     "../ash/profiles/profile_helper.h",
     "../ash/reset/metrics.h",
+    "../ash/scanning/lorgnette_scanner_manager.cc",
+    "../ash/scanning/lorgnette_scanner_manager.h",
+    "../ash/scanning/lorgnette_scanner_manager_factory.cc",
+    "../ash/scanning/lorgnette_scanner_manager_factory.h",
+    "../ash/scanning/lorgnette_scanner_manager_util.cc",
+    "../ash/scanning/lorgnette_scanner_manager_util.h",
+    "../ash/scanning/scan_service.cc",
+    "../ash/scanning/scan_service.h",
+    "../ash/scanning/scan_service_factory.cc",
+    "../ash/scanning/scan_service_factory.h",
+    "../ash/scanning/scanner_detector.h",
+    "../ash/scanning/scanning_paths_provider_impl.cc",
+    "../ash/scanning/scanning_paths_provider_impl.h",
+    "../ash/scanning/scanning_type_converters.cc",
+    "../ash/scanning/scanning_type_converters.h",
+    "../ash/scanning/scanning_util.cc",
+    "../ash/scanning/scanning_util.h",
+    "../ash/scanning/zeroconf_scanner_detector.cc",
+    "../ash/scanning/zeroconf_scanner_detector.h",
+    "../ash/scanning/zeroconf_scanner_detector_utils.cc",
+    "../ash/scanning/zeroconf_scanner_detector_utils.h",
     "../ash/settings/cros_settings.cc",
     "../ash/settings/cros_settings.h",
     "../ash/settings/device_settings_cache.cc",
@@ -2440,6 +2462,8 @@
     "policy/dlp/dlp_data_transfer_notifier.h",
     "policy/dlp/dlp_drag_drop_notifier.cc",
     "policy/dlp/dlp_drag_drop_notifier.h",
+    "policy/dlp/dlp_histogram_helper.cc",
+    "policy/dlp/dlp_histogram_helper.h",
     "policy/dlp/dlp_notification_helper.cc",
     "policy/dlp/dlp_notification_helper.h",
     "policy/dlp/dlp_policy_constants.h",
@@ -2841,27 +2865,6 @@
     "remote_apps/remote_apps_model.cc",
     "remote_apps/remote_apps_model.h",
     "remote_apps/remote_apps_types.h",
-    "scanning/lorgnette_scanner_manager.cc",
-    "scanning/lorgnette_scanner_manager.h",
-    "scanning/lorgnette_scanner_manager_factory.cc",
-    "scanning/lorgnette_scanner_manager_factory.h",
-    "scanning/lorgnette_scanner_manager_util.cc",
-    "scanning/lorgnette_scanner_manager_util.h",
-    "scanning/scan_service.cc",
-    "scanning/scan_service.h",
-    "scanning/scan_service_factory.cc",
-    "scanning/scan_service_factory.h",
-    "scanning/scanner_detector.h",
-    "scanning/scanning_paths_provider_impl.cc",
-    "scanning/scanning_paths_provider_impl.h",
-    "scanning/scanning_type_converters.cc",
-    "scanning/scanning_type_converters.h",
-    "scanning/scanning_util.cc",
-    "scanning/scanning_util.h",
-    "scanning/zeroconf_scanner_detector.cc",
-    "scanning/zeroconf_scanner_detector.h",
-    "scanning/zeroconf_scanner_detector_utils.cc",
-    "scanning/zeroconf_scanner_detector_utils.h",
     "scheduler_configuration_manager.cc",
     "scheduler_configuration_manager.h",
     "secure_channel/nearby_connection_broker.cc",
@@ -3544,6 +3547,16 @@
     "../ash/login/version_updater/version_updater_unittest.cc",
     "../ash/mobile/mobile_activator_unittest.cc",
     "../ash/notifications/deprecation_notification_controller_unittest.cc",
+    "../ash/scanning/fake_lorgnette_scanner_manager.cc",
+    "../ash/scanning/fake_lorgnette_scanner_manager.h",
+    "../ash/scanning/lorgnette_scanner_manager_unittest.cc",
+    "../ash/scanning/lorgnette_scanner_manager_util_unittest.cc",
+    "../ash/scanning/scan_service_factory_unittest.cc",
+    "../ash/scanning/scan_service_unittest.cc",
+    "../ash/scanning/scanning_paths_provider_impl_unittest.cc",
+    "../ash/scanning/scanning_type_converters_unittest.cc",
+    "../ash/scanning/scanning_util_unittest.cc",
+    "../ash/scanning/zeroconf_scanner_detector_unittest.cc",
     "../ash/settings/cros_settings_unittest.cc",
     "../ash/settings/device_settings_cache_unittest.cc",
     "../ash/settings/device_settings_provider_unittest.cc",
@@ -4065,16 +4078,6 @@
     "release_notes/release_notes_notification_unittest.cc",
     "release_notes/release_notes_storage_unittest.cc",
     "remote_apps/remote_apps_model_unittest.cc",
-    "scanning/fake_lorgnette_scanner_manager.cc",
-    "scanning/fake_lorgnette_scanner_manager.h",
-    "scanning/lorgnette_scanner_manager_unittest.cc",
-    "scanning/lorgnette_scanner_manager_util_unittest.cc",
-    "scanning/scan_service_factory_unittest.cc",
-    "scanning/scan_service_unittest.cc",
-    "scanning/scanning_paths_provider_impl_unittest.cc",
-    "scanning/scanning_type_converters_unittest.cc",
-    "scanning/scanning_util_unittest.cc",
-    "scanning/zeroconf_scanner_detector_unittest.cc",
     "scheduler_configuration_manager_unittest.cc",
     "secure_channel/nearby_connection_broker_impl_unittest.cc",
     "secure_channel/nearby_connector_impl_unittest.cc",
diff --git a/chrome/browser/chromeos/extensions/document_scan/document_scan_api.cc b/chrome/browser/chromeos/extensions/document_scan/document_scan_api.cc
index 1c4755d..472b8c0 100644
--- a/chrome/browser/chromeos/extensions/document_scan/document_scan_api.cc
+++ b/chrome/browser/chromeos/extensions/document_scan/document_scan_api.cc
@@ -10,8 +10,8 @@
 #include "base/base64.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager.h"
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager_factory.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.h"
 #include "chromeos/dbus/lorgnette/lorgnette_service.pb.h"
 #include "content/public/browser/browser_context.h"
 #include "third_party/cros_system_api/dbus/lorgnette/dbus-constants.h"
diff --git a/chrome/browser/chromeos/extensions/document_scan/document_scan_api_unittest.cc b/chrome/browser/chromeos/extensions/document_scan/document_scan_api_unittest.cc
index a636a91..93fe94c 100644
--- a/chrome/browser/chromeos/extensions/document_scan/document_scan_api_unittest.cc
+++ b/chrome/browser/chromeos/extensions/document_scan/document_scan_api_unittest.cc
@@ -10,9 +10,9 @@
 #include "base/memory/ref_counted.h"
 #include "base/optional.h"
 #include "base/values.h"
+#include "chrome/browser/ash/scanning/fake_lorgnette_scanner_manager.h"
+#include "chrome/browser/ash/scanning/lorgnette_scanner_manager_factory.h"
 #include "chrome/browser/chromeos/extensions/document_scan/document_scan_api.h"
-#include "chrome/browser/chromeos/scanning/fake_lorgnette_scanner_manager.h"
-#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager_factory.h"
 #include "chrome/browser/extensions/extension_api_unittest.h"
 #include "chrome/browser/extensions/extension_function_test_utils.h"
 #include "components/keyed_service/core/keyed_service.h"
diff --git a/chrome/browser/chromeos/hats/OWNERS b/chrome/browser/chromeos/hats/OWNERS
index 2e8fe8f..dfaa641 100644
--- a/chrome/browser/chromeos/hats/OWNERS
+++ b/chrome/browser/chromeos/hats/OWNERS
@@ -1 +1,2 @@
 malaykeshav@chromium.org
+gabrielsz@google.com
diff --git a/chrome/browser/chromeos/hats/hats_dialog.cc b/chrome/browser/chromeos/hats/hats_dialog.cc
index a5dd6b66..68a2244 100644
--- a/chrome/browser/chromeos/hats/hats_dialog.cc
+++ b/chrome/browser/chromeos/hats/hats_dialog.cc
@@ -104,8 +104,9 @@
   if (!user_locale.length())
     user_locale = kDefaultProfileLocale;
 
-  std::unique_ptr<HatsDialog> hats_dialog(
-      new HatsDialog(HatsFinchHelper::GetTriggerID(), profile));
+  std::unique_ptr<HatsDialog> hats_dialog(new HatsDialog(
+      HatsFinchHelper::GetTriggerID(features::kHappinessTrackingSystem),
+      profile));
 
   // Raw pointer is used here since the dialog is owned by the hats
   // notification controller which lives until the end of the user session. The
diff --git a/chrome/browser/chromeos/hats/hats_finch_helper.cc b/chrome/browser/chromeos/hats/hats_finch_helper.cc
index a4b5fa19..389a574 100644
--- a/chrome/browser/chromeos/hats/hats_finch_helper.cc
+++ b/chrome/browser/chromeos/hats/hats_finch_helper.cc
@@ -27,14 +27,14 @@
 // static
 const char HatsFinchHelper::kTriggerIdParam[] = "trigger_id";
 
-std::string HatsFinchHelper::GetTriggerID() {
-  DCHECK(base::FeatureList::IsEnabled(features::kHappinessTrackingSystem));
-  return base::GetFieldTrialParamValueByFeature(
-      features::kHappinessTrackingSystem, kTriggerIdParam);
+std::string HatsFinchHelper::GetTriggerID(const base::Feature& feature) {
+  DCHECK(base::FeatureList::IsEnabled(feature));
+  return base::GetFieldTrialParamValueByFeature(feature, kTriggerIdParam);
 }
 
-HatsFinchHelper::HatsFinchHelper(Profile* profile) : profile_(profile) {
-  LoadFinchParamValues();
+HatsFinchHelper::HatsFinchHelper(Profile* profile, const base::Feature& feature)
+    : profile_(profile) {
+  LoadFinchParamValues(feature);
 
   // Reset prefs related to survey cycle if the finch seed has the reset param
   // set. Do no futher op until a new finch seed with the reset flags unset is
@@ -52,8 +52,7 @@
 
 HatsFinchHelper::~HatsFinchHelper() {}
 
-void HatsFinchHelper::LoadFinchParamValues() {
-  const auto& feature = features::kHappinessTrackingSystem;
+void HatsFinchHelper::LoadFinchParamValues(const base::Feature& feature) {
   if (!base::FeatureList::IsEnabled(feature))
     return;
 
@@ -86,7 +85,7 @@
   first_survey_start_date_ =
       base::Time().FromJsTime(first_survey_start_date_ms);
 
-  trigger_id_ = GetTriggerID();
+  trigger_id_ = GetTriggerID(feature);
 
   reset_survey_cycle_ = base::GetFieldTrialParamByFeatureAsBool(
       feature, kResetSurveyCycleParam, false);
diff --git a/chrome/browser/chromeos/hats/hats_finch_helper.h b/chrome/browser/chromeos/hats/hats_finch_helper.h
index 734c3c7..c1351d3 100644
--- a/chrome/browser/chromeos/hats/hats_finch_helper.h
+++ b/chrome/browser/chromeos/hats/hats_finch_helper.h
@@ -10,6 +10,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/time/time.h"
+#include "chrome/common/chrome_features.h"
 
 class Profile;
 
@@ -19,9 +20,9 @@
 // information related to the hats finch experiment.
 class HatsFinchHelper {
  public:
-  static std::string GetTriggerID();
+  static std::string GetTriggerID(const base::Feature& feature);
 
-  explicit HatsFinchHelper(Profile* profile);
+  explicit HatsFinchHelper(Profile* profile, const base::Feature& feature);
   ~HatsFinchHelper();
 
   bool IsDeviceSelectedForCurrentCycle() const {
@@ -45,7 +46,7 @@
 
   // Loads all the param values from the finch seed and initializes the member
   // variables.
-  void LoadFinchParamValues();
+  void LoadFinchParamValues(const base::Feature& feature);
 
   // Returns true if the survey cycle that was active most recently has passed
   // its end date.
diff --git a/chrome/browser/chromeos/hats/hats_finch_helper_unittest.cc b/chrome/browser/chromeos/hats/hats_finch_helper_unittest.cc
index 6237c90..70eb075 100644
--- a/chrome/browser/chromeos/hats/hats_finch_helper_unittest.cc
+++ b/chrome/browser/chromeos/hats/hats_finch_helper_unittest.cc
@@ -65,7 +65,8 @@
       "1.0", "7", "1475613895337", "false", "false", kValidTriggerId);
   SetFeatureParams(params);
 
-  HatsFinchHelper hats_finch_helper(&profile_);
+  HatsFinchHelper hats_finch_helper(&profile_,
+                                    features::kHappinessTrackingSystem);
 
   EXPECT_EQ(hats_finch_helper.probability_of_pick_, 1.0);
   EXPECT_EQ(hats_finch_helper.survey_cycle_length_, 7);
@@ -82,7 +83,8 @@
   SetFeatureParams(params);
 
   base::Time current_time = base::Time::Now();
-  HatsFinchHelper hats_finch_helper(&profile_);
+  HatsFinchHelper hats_finch_helper(&profile_,
+                                    features::kHappinessTrackingSystem);
 
   EXPECT_EQ(hats_finch_helper.probability_of_pick_, 0.0);
   EXPECT_EQ(hats_finch_helper.survey_cycle_length_, INT_MAX);
@@ -100,7 +102,8 @@
 
   base::Time current_time = base::Time::Now();
 
-  HatsFinchHelper hats_finch_helper(&profile_);
+  HatsFinchHelper hats_finch_helper(&profile_,
+                                    features::kHappinessTrackingSystem);
 
   // Case 1
   base::Time start_date = current_time - base::TimeDelta::FromDays(10);
@@ -132,7 +135,8 @@
                          initial_timestamp);
 
   base::Time current_time = base::Time::Now();
-  HatsFinchHelper hats_finch_helper(&profile_);
+  HatsFinchHelper hats_finch_helper(&profile_,
+                                    features::kHappinessTrackingSystem);
 
   EXPECT_EQ(hats_finch_helper.probability_of_pick_, 0);
   EXPECT_EQ(hats_finch_helper.survey_cycle_length_, INT_MAX);
@@ -158,7 +162,8 @@
                          initial_timestamp);
 
   base::Time current_time = base::Time::Now();
-  HatsFinchHelper hats_finch_helper(&profile_);
+  HatsFinchHelper hats_finch_helper(&profile_,
+                                    features::kHappinessTrackingSystem);
 
   EXPECT_EQ(hats_finch_helper.probability_of_pick_, 0);
   EXPECT_EQ(hats_finch_helper.survey_cycle_length_, INT_MAX);
diff --git a/chrome/browser/chromeos/hats/hats_notification_controller.cc b/chrome/browser/chromeos/hats/hats_notification_controller.cc
index 195afe7e..db0e109f 100644
--- a/chrome/browser/chromeos/hats/hats_notification_controller.cc
+++ b/chrome/browser/chromeos/hats/hats_notification_controller.cc
@@ -162,7 +162,8 @@
     return false;
 
   // Call finch helper only after all the profile checks are complete.
-  HatsFinchHelper hats_finch_helper(profile);
+  HatsFinchHelper hats_finch_helper(profile,
+                                    features::kHappinessTrackingSystem);
   if (!hats_finch_helper.IsDeviceSelectedForCurrentCycle())
     return false;
 
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys_service_factory.cc b/chrome/browser/chromeos/platform_keys/platform_keys_service_factory.cc
index fa57165..1624f14 100644
--- a/chrome/browser/chromeos/platform_keys/platform_keys_service_factory.cc
+++ b/chrome/browser/chromeos/platform_keys/platform_keys_service_factory.cc
@@ -46,14 +46,14 @@
 void GetCertDatabaseOnIoThread(
     const scoped_refptr<base::SingleThreadTaskRunner>& origin_task_runner,
     PlatformKeysServiceImplDelegate::OnGotNSSCertDatabase callback,
-    content::ResourceContext* context) {
+    NssCertDatabaseGetter database_getter) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   base::RepeatingCallback<void(net::NSSCertDatabase*)> on_got_on_io_thread =
       base::BindRepeating(&DidGetCertDbOnIoThread, origin_task_runner,
                           base::Passed(&callback));
   net::NSSCertDatabase* cert_db =
-      GetNSSCertDatabaseForResourceContext(context, on_got_on_io_thread);
+      std::move(database_getter).Run(on_got_on_io_thread);
 
   if (cert_db)
     on_got_on_io_thread.Run(cert_db);
@@ -72,7 +72,7 @@
         FROM_HERE,
         base::BindOnce(&GetCertDatabaseOnIoThread,
                        base::ThreadTaskRunnerHandle::Get(), std::move(callback),
-                       browser_context_->GetResourceContext()));
+                       CreateNSSCertDatabaseGetter(browser_context_)));
   }
 
   std::unique_ptr<net::ClientCertStore> CreateClientCertStore() override {
diff --git a/chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller.cc b/chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller.cc
index bf934fa..3310de6 100644
--- a/chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller.cc
+++ b/chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller.cc
@@ -7,6 +7,7 @@
 #include "base/check_op.h"
 #include "base/notreached.h"
 #include "base/syslog_logging.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -117,8 +118,10 @@
 
 // static
 void DataTransferDlpController::Init(const DlpRulesManager& dlp_rules_manager) {
-  if (!HasInstance())
+  if (!HasInstance()) {
+    DlpBooleanHistogram(dlp::kDataTransferControllerStartedUMA, true);
     new DataTransferDlpController(dlp_rules_manager);
+  }
 }
 
 bool DataTransferDlpController::IsClipboardReadAllowed(
@@ -163,6 +166,7 @@
     default:
       break;
   }
+  DlpBooleanHistogram(dlp::kClipboardReadBlockedUMA, !is_read_allowed);
   return is_read_allowed;
 }
 
@@ -217,7 +221,9 @@
     NotifyBlockedPaste(data_src, data_dst);
   }
 
-  return level == DlpRulesManager::Level::kAllow;
+  const bool is_drop_allowed = level == DlpRulesManager::Level::kAllow;
+  DlpBooleanHistogram(dlp::kDragDropBlockedUMA, !is_drop_allowed);
+  return is_drop_allowed;
 }
 
 DataTransferDlpController::DataTransferDlpController(
diff --git a/chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller_unittest.cc b/chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller_unittest.cc
index ed1ef4b..162d12a 100644
--- a/chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller_unittest.cc
+++ b/chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller_unittest.cc
@@ -7,7 +7,9 @@
 #include <memory>
 
 #include "base/optional.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_callback.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/chrome_constants.h"
@@ -121,18 +123,25 @@
   content::BrowserTaskEnvironment task_environment_;
   ::testing::StrictMock<MockDlpRulesManager> rules_manager_;
   ::testing::StrictMock<MockDlpController> dlp_controller_;
+  base::HistogramTester histogram_tester_;
 };
 
 TEST_F(DataTransferDlpControllerTest, NullSrc) {
   EXPECT_EQ(true, dlp_controller_.IsClipboardReadAllowed(nullptr, nullptr));
   EXPECT_EQ(true, dlp_controller_.IsDragDropAllowed(nullptr, nullptr,
                                                     /*is_drop=*/false));
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kClipboardReadBlockedUMA, false, 1);
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kDragDropBlockedUMA, false, 1);
 }
 
 TEST_F(DataTransferDlpControllerTest, ClipboardHistoryDst) {
   ui::DataTransferEndpoint data_src(url::Origin::Create(GURL(kExample1Url)));
   ui::DataTransferEndpoint data_dst(ui::EndpointType::kClipboardHistory);
   EXPECT_EQ(true, dlp_controller_.IsClipboardReadAllowed(&data_src, &data_dst));
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kClipboardReadBlockedUMA, false, 1);
 }
 
 TEST_F(DataTransferDlpControllerTest, PasteIfAllowed_Allow) {
@@ -267,6 +276,10 @@
   EXPECT_EQ(true, dlp_controller_.IsDragDropAllowed(&data_src, dst_ptr,
                                                     /*is_drop=*/do_notify));
   testing::Mock::VerifyAndClearExpectations(&dlp_controller_);
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kClipboardReadBlockedUMA, false, 1);
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kDragDropBlockedUMA, false, 1);
 }
 
 TEST_P(DlpControllerTest, Block) {
@@ -296,6 +309,10 @@
   EXPECT_EQ(false, dlp_controller_.IsDragDropAllowed(&data_src, dst_ptr,
                                                      /*is_drop=*/do_notify));
   testing::Mock::VerifyAndClearExpectations(&dlp_controller_);
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kClipboardReadBlockedUMA, true, 1);
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kDragDropBlockedUMA, true, 1);
 }
 
 TEST_P(DlpControllerTest, Warn) {
@@ -331,6 +348,12 @@
       .WillRepeatedly(testing::Return(false));
   EXPECT_EQ(true, dlp_controller_.IsClipboardReadAllowed(&data_src, dst_ptr));
   testing::Mock::VerifyAndClearExpectations(&dlp_controller_);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kClipboardReadBlockedUMA, false,
+      show_warning ? 1 : 2);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kClipboardReadBlockedUMA, true,
+      show_warning ? 1 : 0);
 }
 
 TEST_P(DlpControllerTest, Warn_ShouldCancelOnWarn) {
@@ -387,6 +410,10 @@
   EXPECT_EQ(true, dlp_controller_.IsDragDropAllowed(&data_src, &data_dst,
                                                     /*is_drop=*/do_notify));
   testing::Mock::VerifyAndClearExpectations(&dlp_controller_);
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kClipboardReadBlockedUMA, false, 1);
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kDragDropBlockedUMA, false, 1);
 }
 
 TEST_P(DlpControllerVMsTest, Block) {
@@ -416,6 +443,10 @@
   EXPECT_EQ(false, dlp_controller_.IsDragDropAllowed(&data_src, &data_dst,
                                                      /*is_drop=*/do_notify));
   testing::Mock::VerifyAndClearExpectations(&dlp_controller_);
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kClipboardReadBlockedUMA, true, 1);
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kDragDropBlockedUMA, true, 1);
 }
 
 TEST_P(DlpControllerVMsTest, Warn) {
@@ -434,6 +465,8 @@
 
   EXPECT_EQ(true, dlp_controller_.IsClipboardReadAllowed(&data_src, &data_dst));
   testing::Mock::VerifyAndClearExpectations(&dlp_controller_);
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kClipboardReadBlockedUMA, false, 1);
 }
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager.cc b/chrome/browser/chromeos/policy/dlp/dlp_content_manager.cc
index cb1102e..dd1b5c1 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_content_manager.cc
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager.cc
@@ -13,6 +13,7 @@
 #include "base/stl_util.h"
 #include "base/syslog_logging.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_notification_helper.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h"
@@ -67,6 +68,7 @@
       IsAreaRestricted(area, DlpContentRestriction::kScreenshot);
   if (restricted)
     SYSLOG(INFO) << "DLP blocked taking a screenshot";
+  DlpBooleanHistogram(dlp::kScreenshotBlockedUMA, restricted);
   return restricted;
 }
 
@@ -76,6 +78,7 @@
       IsAreaRestricted(area, DlpContentRestriction::kVideoCapture);
   if (restricted)
     SYSLOG(INFO) << "DLP blocked taking a video capture";
+  DlpBooleanHistogram(dlp::kVideoCaptureBlockedUMA, restricted);
   return restricted;
 }
 
@@ -92,6 +95,7 @@
                               .HasRestriction(DlpContentRestriction::kPrint);
   if (restricted)
     SYSLOG(INFO) << "DLP blocked printing";
+  DlpBooleanHistogram(dlp::kPrintingBlockedUMA, restricted);
   return restricted;
 }
 
@@ -102,6 +106,7 @@
         DlpContentRestriction::kScreenShare);
     if (restricted)
       SYSLOG(INFO) << "DLP blocked screen sharing";
+    DlpBooleanHistogram(dlp::kScreenShareBlockedUMA, restricted);
     return restricted;
   }
 
@@ -117,12 +122,14 @@
             .HasRestriction(DlpContentRestriction::kScreenShare);
     if (restricted)
       SYSLOG(INFO) << "DLP blocked screen sharing";
+    DlpBooleanHistogram(dlp::kScreenShareBlockedUMA, restricted);
     return restricted;
   }
 
   DCHECK_EQ(media_id.type, content::DesktopMediaID::Type::TYPE_WINDOW);
   aura::Window* window = content::DesktopMediaID::GetNativeWindowById(media_id);
   if (!window) {
+    DlpBooleanHistogram(dlp::kScreenShareBlockedUMA, false);
     return false;
   }
   for (auto& entry : confidential_web_contents_) {
@@ -130,10 +137,12 @@
     if (entry.second.HasRestriction(DlpContentRestriction::kScreenShare) &&
         window->Contains(web_contents_window)) {
       SYSLOG(INFO) << "DLP blocked screen sharing";
+      DlpBooleanHistogram(dlp::kScreenShareBlockedUMA, true);
       return true;
     }
   }
 
+  DlpBooleanHistogram(dlp::kScreenShareBlockedUMA, false);
   return false;
 }
 
@@ -158,6 +167,7 @@
                               DlpContentRestriction::kVideoCapture);
   if (restricted)
     SYSLOG(INFO) << "DLP blocked taking a screen capture";
+  DlpBooleanHistogram(dlp::kCaptureModeInitBlockedUMA, restricted);
   return restricted;
 }
 
@@ -331,6 +341,7 @@
   if (added_restrictions.HasRestriction(
           DlpContentRestriction::kPrivacyScreen)) {
     SYSLOG(INFO) << "DLP enforced privacy screen";
+    DlpBooleanHistogram(dlp::kPrivacyScreenEnforcedUMA, true);
     ash::PrivacyScreenDlpHelper::Get()->SetEnforced(true);
   }
 
@@ -348,6 +359,7 @@
   if (!GetOnScreenPresentRestrictions().HasRestriction(
           DlpContentRestriction::kPrivacyScreen)) {
     SYSLOG(INFO) << "DLP removed enforcement of privacy screen";
+    DlpBooleanHistogram(dlp::kPrivacyScreenEnforcedUMA, false);
     ash::PrivacyScreenDlpHelper::Get()->SetEnforced(false);
   }
 }
@@ -414,6 +426,7 @@
                        DlpContentRestriction::kVideoCapture)) {
     if (ash::features::IsCaptureModeEnabled()) {
       SYSLOG(INFO) << "DLP interrupted screen recording";
+      DlpBooleanHistogram(dlp::kVideoCaptureInterruptedUMA, true);
       ChromeCaptureModeDelegate::Get()->InterruptVideoRecordingIfAny();
     }
     running_video_capture_area_.reset();
@@ -457,6 +470,7 @@
     if (is_allowed != capture.is_running) {
       SYSLOG(INFO) << "DLP " << (is_allowed ? "resumed" : "paused")
                    << " running screen share";
+      DlpBooleanHistogram(dlp::kScreenSharePausedOrResumedUMA, !is_allowed);
       capture.state_change_callback.Run(
           capture.media_id, capture.is_running
                                 ? blink::mojom::MediaStreamStateChange::PAUSE
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_browsertest.cc b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_browsertest.cc
index b6d49f1c..1893d8d6 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_browsertest.cc
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_browsertest.cc
@@ -7,9 +7,11 @@
 #include "ash/public/cpp/ash_features.h"
 #include "base/json/json_writer.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_content_manager_test_helper.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_policy_constants.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_test_utils.h"
 #include "chrome/browser/chromeos/policy/login_policy_test_base.h"
@@ -71,6 +73,7 @@
 
  protected:
   DlpContentManagerTestHelper helper_;
+  base::HistogramTester histogram_tester_;
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
@@ -100,12 +103,20 @@
   EXPECT_FALSE(manager->IsScreenshotRestricted(window));
   EXPECT_FALSE(manager->IsScreenshotRestricted(partial_in));
   EXPECT_FALSE(manager->IsScreenshotRestricted(partial_out));
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenshotBlockedUMA, true, 0);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenshotBlockedUMA, false, 4);
 
   helper_.ChangeConfidentiality(web_contents, kScreenshotRestricted);
   EXPECT_TRUE(manager->IsScreenshotRestricted(fullscreen));
   EXPECT_TRUE(manager->IsScreenshotRestricted(window));
   EXPECT_TRUE(manager->IsScreenshotRestricted(partial_in));
   EXPECT_FALSE(manager->IsScreenshotRestricted(partial_out));
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenshotBlockedUMA, true, 3);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenshotBlockedUMA, false, 5);
 
   web_contents->WasHidden();
   helper_.ChangeVisibility(web_contents);
@@ -113,6 +124,10 @@
   EXPECT_TRUE(manager->IsScreenshotRestricted(window));
   EXPECT_FALSE(manager->IsScreenshotRestricted(partial_in));
   EXPECT_FALSE(manager->IsScreenshotRestricted(partial_out));
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenshotBlockedUMA, true, 4);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenshotBlockedUMA, false, 8);
 
   web_contents->WasShown();
   helper_.ChangeVisibility(web_contents);
@@ -120,11 +135,19 @@
   EXPECT_TRUE(manager->IsScreenshotRestricted(window));
   EXPECT_TRUE(manager->IsScreenshotRestricted(partial_in));
   EXPECT_FALSE(manager->IsScreenshotRestricted(partial_out));
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenshotBlockedUMA, true, 7);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenshotBlockedUMA, false, 9);
 
   helper_.DestroyWebContents(web_contents);
   EXPECT_FALSE(manager->IsScreenshotRestricted(fullscreen));
   EXPECT_FALSE(manager->IsScreenshotRestricted(partial_in));
   EXPECT_FALSE(manager->IsScreenshotRestricted(partial_out));
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenshotBlockedUMA, true, 7);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenshotBlockedUMA, false, 12);
 }
 
 IN_PROC_BROWSER_TEST_F(DlpContentManagerBrowserTest,
@@ -167,6 +190,8 @@
 
   capture_mode_delegate->StopObservingRestrictedContent();
   browser2->window()->Close();
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kVideoCaptureInterruptedUMA, true, 1);
 }
 
 IN_PROC_BROWSER_TEST_F(DlpContentManagerBrowserTest,
@@ -209,6 +234,8 @@
 
   capture_mode_delegate->StopObservingRestrictedContent();
   browser2->window()->Close();
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kVideoCaptureInterruptedUMA, true, 1);
 }
 
 IN_PROC_BROWSER_TEST_F(DlpContentManagerBrowserTest,
@@ -253,6 +280,8 @@
   capture_mode_delegate->StopObservingRestrictedContent();
 
   browser2->window()->Close();
+  histogram_tester_.ExpectTotalCount(
+      GetDlpHistogramPrefix() + dlp::kVideoCaptureInterruptedUMA, 0);
 }
 
 IN_PROC_BROWSER_TEST_F(DlpContentManagerBrowserTest,
@@ -273,6 +302,10 @@
       kScreenCapturePausedNotificationId));
   EXPECT_FALSE(display_service_tester.GetNotification(
       kScreenCaptureResumedNotificationId));
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenSharePausedOrResumedUMA, true, 0);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenSharePausedOrResumedUMA, false, 0);
 
   helper_.ChangeConfidentiality(web_contents, kScreenShareRestricted);
 
@@ -280,6 +313,10 @@
       kScreenCapturePausedNotificationId));
   EXPECT_FALSE(display_service_tester.GetNotification(
       kScreenCaptureResumedNotificationId));
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenSharePausedOrResumedUMA, true, 1);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenSharePausedOrResumedUMA, false, 0);
 
   helper_.ChangeConfidentiality(web_contents, kEmptyRestrictionSet);
 
@@ -287,6 +324,10 @@
       kScreenCapturePausedNotificationId));
   EXPECT_TRUE(display_service_tester.GetNotification(
       kScreenCaptureResumedNotificationId));
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenSharePausedOrResumedUMA, true, 1);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenSharePausedOrResumedUMA, false, 1);
 
   manager->OnScreenCaptureStopped("label", media_id);
 
@@ -294,6 +335,10 @@
       kScreenCapturePausedNotificationId));
   EXPECT_FALSE(display_service_tester.GetNotification(
       kScreenCaptureResumedNotificationId));
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenSharePausedOrResumedUMA, true, 1);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kScreenSharePausedOrResumedUMA, false, 1);
 }
 
 class DlpContentManagerPolicyBrowserTest : public LoginPolicyTestBase {
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_unittest.cc b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_unittest.cc
index f80e386..da1f5c36 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_unittest.cc
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_unittest.cc
@@ -5,8 +5,10 @@
 #include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
 
 #include "ash/public/cpp/privacy_screen_dlp_helper.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_content_manager_test_helper.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_renderer_host.h"
@@ -54,6 +56,7 @@
   DlpContentManager* manager_ = nullptr;
   content::BrowserTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::HistogramTester histogram_tester_;
 
  private:
   content::RenderViewHostTestEnabler rvh_test_enabler_;
@@ -186,22 +189,38 @@
   testing::Mock::VerifyAndClearExpectations(&mock_privacy_screen_helper);
   EXPECT_CALL(mock_privacy_screen_helper, SetEnforced(true)).Times(1);
   helper_.ChangeConfidentiality(web_contents.get(), kPrivacyScreenEnforced);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrivacyScreenEnforcedUMA, true, 1);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrivacyScreenEnforcedUMA, false, 0);
 
   testing::Mock::VerifyAndClearExpectations(&mock_privacy_screen_helper);
   EXPECT_CALL(mock_privacy_screen_helper, SetEnforced(false)).Times(1);
   web_contents->WasHidden();
   helper_.ChangeVisibility(web_contents.get());
   task_environment_.FastForwardBy(helper_.GetPrivacyScreenOffDelay());
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrivacyScreenEnforcedUMA, true, 1);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrivacyScreenEnforcedUMA, false, 1);
 
   testing::Mock::VerifyAndClearExpectations(&mock_privacy_screen_helper);
   EXPECT_CALL(mock_privacy_screen_helper, SetEnforced(true)).Times(1);
   web_contents->WasShown();
   helper_.ChangeVisibility(web_contents.get());
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrivacyScreenEnforcedUMA, true, 2);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrivacyScreenEnforcedUMA, false, 1);
 
   testing::Mock::VerifyAndClearExpectations(&mock_privacy_screen_helper);
   EXPECT_CALL(mock_privacy_screen_helper, SetEnforced(false)).Times(1);
   helper_.DestroyWebContents(web_contents.get());
   task_environment_.FastForwardBy(helper_.GetPrivacyScreenOffDelay());
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrivacyScreenEnforcedUMA, true, 2);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrivacyScreenEnforcedUMA, false, 2);
 }
 
 TEST_F(DlpContentManagerTest, PrintingRestricted) {
@@ -209,15 +228,27 @@
   EXPECT_EQ(manager_->GetConfidentialRestrictions(web_contents.get()),
             kEmptyRestrictionSet);
   EXPECT_FALSE(manager_->IsPrintingRestricted(web_contents.get()));
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrintingBlockedUMA, true, 0);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrintingBlockedUMA, false, 1);
 
   helper_.ChangeConfidentiality(web_contents.get(), kPrintingRestricted);
   EXPECT_EQ(manager_->GetConfidentialRestrictions(web_contents.get()),
             kPrintingRestricted);
   EXPECT_TRUE(manager_->IsPrintingRestricted(web_contents.get()));
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrintingBlockedUMA, true, 1);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrintingBlockedUMA, false, 1);
 
   helper_.DestroyWebContents(web_contents.get());
   EXPECT_EQ(manager_->GetConfidentialRestrictions(web_contents.get()),
             kEmptyRestrictionSet);
   EXPECT_FALSE(manager_->IsPrintingRestricted(web_contents.get()));
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrintingBlockedUMA, true, 1);
+  histogram_tester_.ExpectBucketCount(
+      GetDlpHistogramPrefix() + dlp::kPrintingBlockedUMA, false, 2);
 }
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.cc b/chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.cc
new file mode 100644
index 0000000..23f0e0c
--- /dev/null
+++ b/chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.cc
@@ -0,0 +1,27 @@
+// 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/chromeos/policy/dlp/dlp_histogram_helper.h"
+
+#include <string>
+
+#include "base/metrics/histogram_functions.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
+
+namespace policy {
+
+std::string GetDlpHistogramPrefix() {
+  return "Enterprise.Dlp.";
+}
+
+void DlpBooleanHistogram(const std::string& suffix, bool value) {
+  base::UmaHistogramBoolean(GetDlpHistogramPrefix() + suffix, value);
+}
+
+void DlpRestrictionConfiguredHistogram(DlpRulesManager::Restriction value) {
+  base::UmaHistogramEnumeration(
+      GetDlpHistogramPrefix() + "RestrictionConfigured", value);
+}
+
+}  // namespace policy
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.h b/chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.h
new file mode 100644
index 0000000..892dca2
--- /dev/null
+++ b/chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.h
@@ -0,0 +1,42 @@
+// 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_CHROMEOS_POLICY_DLP_DLP_HISTOGRAM_HELPER_H_
+#define CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_HISTOGRAM_HELPER_H_
+
+#include <string>
+
+#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
+
+namespace policy {
+
+namespace dlp {
+
+// Constants with UMA histogram name suffixes.
+constexpr char kCaptureModeInitBlockedUMA[] = "CaptureModeInitBlocked";
+constexpr char kClipboardReadBlockedUMA[] = "ClipboardReadBlocked";
+constexpr char kDataTransferControllerStartedUMA[] =
+    "DataTransferControllerStarted";
+constexpr char kDlpPolicyPresentUMA[] = "DlpPolicyPresent";
+constexpr char kDragDropBlockedUMA[] = "DragDropBlocked";
+constexpr char kFilesDaemonStartedUMA[] = "FilesDaemonStarted";
+constexpr char kPrintingBlockedUMA[] = "PrintingBlocked";
+constexpr char kPrivacyScreenEnforcedUMA[] = "PrivacyScreenEnforced";
+constexpr char kScreenShareBlockedUMA[] = "ScreenShareBlocked";
+constexpr char kScreenSharePausedOrResumedUMA[] = "ScreenSharePausedOrResumed";
+constexpr char kScreenshotBlockedUMA[] = "ScreenshotBlocked";
+constexpr char kVideoCaptureInterruptedUMA[] = "VideoCaptureInterrupted";
+constexpr char kVideoCaptureBlockedUMA[] = "VideoCaptureBlocked";
+
+}  // namespace dlp
+
+std::string GetDlpHistogramPrefix();
+
+void DlpBooleanHistogram(const std::string& suffix, bool value);
+
+void DlpRestrictionConfiguredHistogram(DlpRulesManager::Restriction value);
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_HISTOGRAM_HELPER_H_
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h b/chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h
index 2a81ab2..c06ddfc 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h
+++ b/chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h
@@ -17,6 +17,10 @@
 class DlpRulesManager : public KeyedService {
  public:
   // A restriction that can be set by DataLeakPreventionRulesList policy.
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  // When new entries are added, EnterpriseDlpPolicyRestriction enum in
+  // histograms/enums.xml should be updated.
   enum class Restriction {
     kUnknownRestriction = 0,
     kClipboard = 1,      // Restricts sharing the data via clipboard and
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_rules_manager_impl.cc b/chrome/browser/chromeos/policy/dlp/dlp_rules_manager_impl.cc
index ca95f93..3b4adcc 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_rules_manager_impl.cc
+++ b/chrome/browser/chromeos/policy/dlp/dlp_rules_manager_impl.cc
@@ -16,6 +16,7 @@
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/policy/dlp/data_transfer_dlp_controller.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_policy_constants.h"
 #include "chrome/common/chrome_features.h"
 #include "chromeos/dbus/dlp/dlp_client.h"
@@ -233,7 +234,9 @@
   const base::ListValue* rules_list =
       g_browser_process->local_state()->GetList(policy_prefs::kDlpRulesList);
 
-  if (!rules_list) {
+  DlpBooleanHistogram(dlp::kDlpPolicyPresentUMA,
+                      rules_list && !rules_list->empty());
+  if (!rules_list || rules_list->empty()) {
     DataTransferDlpController::DeleteInstance();
     return;
   }
@@ -314,6 +317,7 @@
         request_to_daemon.mutable_rules()->Add(std::move(files_rule));
       }
 
+      DlpRestrictionConfiguredHistogram(rule_restriction);
       restrictions_map_[rule_restriction].emplace(rules_counter, rule_level);
     }
     ++rules_counter;
@@ -327,6 +331,7 @@
 
   // TODO(crbug.com/1174501) Shutdown the daemon when restrictions are empty.
   if (request_to_daemon.rules_size() > 0) {
+    DlpBooleanHistogram(dlp::kFilesDaemonStartedUMA, true);
     chromeos::DlpClient::Get()->SetDlpFilesPolicy(
         request_to_daemon, base::BindOnce(&OnSetDlpFilesPolicy));
   }
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_rules_manager_impl_unittest.cc b/chrome/browser/chromeos/policy/dlp/dlp_rules_manager_impl_unittest.cc
index 6f10aa3..00fea2e4 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_rules_manager_impl_unittest.cc
+++ b/chrome/browser/chromeos/policy/dlp/dlp_rules_manager_impl_unittest.cc
@@ -7,8 +7,10 @@
 #include <string>
 
 #include "base/strings/strcat.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
+#include "chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_policy_constants.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_test_utils.h"
 #include "chrome/common/chrome_features.h"
@@ -74,6 +76,7 @@
 
   ScopedTestingLocalState testing_local_state_;
   DlpRulesManagerImpl dlp_rules_manager_;
+  base::HistogramTester histogram_tester_;
 };
 
 TEST_F(DlpRulesManagerImplTest, EmptyPref) {
@@ -86,6 +89,8 @@
             dlp_rules_manager_.IsRestrictedDestination(
                 GURL(kExampleUrl), GURL(kGoogleUrl),
                 DlpRulesManager::Restriction::kClipboard));
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kDlpPolicyPresentUMA, false, 1);
 }
 
 TEST_F(DlpRulesManagerImplTest, BlockPriority) {
@@ -140,6 +145,14 @@
   EXPECT_EQ(DlpRulesManager::Level::kBlock,
             dlp_rules_manager_.IsRestricted(
                 GURL(kExampleUrl), DlpRulesManager::Restriction::kScreenshot));
+  histogram_tester_.ExpectUniqueSample(
+      GetDlpHistogramPrefix() + dlp::kDlpPolicyPresentUMA, true, 1);
+  histogram_tester_.ExpectBucketCount("Enterprise.Dlp.RestrictionConfigured",
+                                      DlpRulesManager::Restriction::kClipboard,
+                                      2);
+  histogram_tester_.ExpectBucketCount("Enterprise.Dlp.RestrictionConfigured",
+                                      DlpRulesManager::Restriction::kScreenshot,
+                                      1);
 
   // Clear pref
   UpdatePolicyPref(base::Value(base::Value::Type::LIST));
diff --git a/chrome/browser/chromeos/policy/extension_install_event_log_uploader_unittest.cc b/chrome/browser/chromeos/policy/extension_install_event_log_uploader_unittest.cc
index c8f47b98..4aa2876f 100644
--- a/chrome/browser/chromeos/policy/extension_install_event_log_uploader_unittest.cc
+++ b/chrome/browser/chromeos/policy/extension_install_event_log_uploader_unittest.cc
@@ -25,6 +25,7 @@
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "components/reporting/client/mock_report_queue.h"
 #include "components/reporting/util/status.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -56,57 +57,6 @@
   return arg == expected_serialized_string;
 }
 
-class TestCallbackWaiter {
- public:
-  TestCallbackWaiter() : run_loop_(std::make_unique<base::RunLoop>()) {}
-
-  virtual void Signal() { run_loop_->Quit(); }
-  virtual void Wait() { run_loop_->Run(); }
-
- protected:
-  std::unique_ptr<base::RunLoop> run_loop_;
-};
-
-class TestCallbackWaiterWithCounter : public TestCallbackWaiter {
- public:
-  explicit TestCallbackWaiterWithCounter(size_t counter_limit)
-      : counter_limit_(counter_limit) {
-    DCHECK_GE(counter_limit, 0u);
-  }
-
-  void Signal() override {
-    const size_t old_count = counter_limit_.fetch_sub(1);
-    DCHECK_GE(old_count, 0u);
-    if (old_count > 1) {
-      return;
-    }
-    run_loop_->Quit();
-  }
-
-  void Wait() override {
-    if (counter_limit_ == 0) {
-      return;
-    }
-    run_loop_->Run();
-  }
-
-  void Reset() {
-    counter_limit_ = 0;
-    run_loop_.reset();
-    run_loop_ = std::make_unique<base::RunLoop>();
-  }
-
-  void WaitAndReset() {
-    Wait();
-    Reset();
-  }
-
-  void IncreaseCounterLimit() { counter_limit_++; }
-
- private:
-  std::atomic<size_t> counter_limit_;
-};
-
 class MockExtensionInstallEventLogUploaderDelegate
     : public ExtensionInstallEventLogUploader::Delegate {
  public:
@@ -128,14 +78,23 @@
  protected:
   ExtensionInstallEventLogUploaderTest() = default;
 
-  void SetUp() override { CreateUploader(); }
+  void SetUp() override {
+    CreateUploader();
+    waiter_ = std::make_unique<reporting::test::TestCallbackWaiter>();
+  }
 
   void TearDown() override {
+    waiter_->Wait();
     Mock::VerifyAndClearExpectations(mock_report_queue_);
     Mock::VerifyAndClearExpectations(&delegate_);
     uploader_.reset();
   }
 
+  void WaitAndReset() {
+    waiter_->Wait();
+    waiter_ = std::make_unique<reporting::test::TestCallbackWaiter>();
+  }
+
   void CreateUploader() {
     uploader_ = std::make_unique<ExtensionInstallEventLogUploader>(
         /*profile=*/nullptr);
@@ -147,22 +106,22 @@
   }
 
   void CompleteSerialize() {
-    waiter_.IncreaseCounterLimit();
+    waiter_->Attach();
     EXPECT_CALL(delegate_, SerializeExtensionLogForUpload_(_))
         .WillOnce(WithArgs<0>(
             Invoke([=](ExtensionInstallEventLogUploader::Delegate::
                            ExtensionLogSerializationCallback& callback) {
               std::move(callback).Run(&log_);
-              waiter_.Signal();
+              waiter_->Signal();
             })));
   }
 
   void CaptureSerialize(ExtensionInstallEventLogUploader::Delegate::
                             ExtensionLogSerializationCallback* callback) {
-    waiter_.IncreaseCounterLimit();
+    waiter_->Attach();
     EXPECT_CALL(delegate_, SerializeExtensionLogForUpload_(_))
         .WillOnce(
-            DoAll(MoveArg<0>(callback), Invoke([=]() { waiter_.Signal(); })));
+            DoAll(MoveArg<0>(callback), Invoke([=]() { waiter_->Signal(); })));
   }
 
   void ClearReportDict() {
@@ -180,7 +139,7 @@
     value_report_ = RealtimeReportingJobConfiguration::BuildReport(
         std::move(events), std::move(context));
 
-    waiter_.IncreaseCounterLimit();
+    waiter_->Attach();
 
     EXPECT_CALL(*mock_report_queue_,
                 AddRecord(MatchEvents(&value_report_), _, _))
@@ -192,7 +151,7 @@
                           : reporting::Status(reporting::error::INTERNAL,
                                               "Failing for tests");
               std::move(callback).Run(status);
-              waiter_.Signal();
+              waiter_->Signal();
 
               // In the real ReportEnqueue::ValueEnqueue call this status return
               // would indicate the that storage module is unavailable. From
@@ -231,9 +190,9 @@
   }
 
   void ExpectExtensionLogUploadSuccess() {
-    waiter_.IncreaseCounterLimit();
+    waiter_->Attach();
     EXPECT_CALL(delegate_, OnExtensionLogUploadSuccess())
-        .WillOnce(Invoke([=]() { waiter_.Signal(); }));
+        .WillOnce(Invoke([=]() { waiter_->Signal(); }));
   }
 
   // Setup retry by serializing event, but failing to upload.
@@ -241,7 +200,9 @@
     CompleteSerializeAndUpload(false /* success */);
     EXPECT_CALL(delegate_, OnExtensionLogUploadSuccess()).Times(0);
     uploader_->RequestUpload();
-    waiter_.WaitAndReset();
+
+    WaitAndReset();
+
     Mock::VerifyAndClearExpectations(&delegate_);
     Mock::VerifyAndClearExpectations(mock_report_queue_);
 
@@ -264,7 +225,7 @@
 
   chromeos::system::ScopedFakeStatisticsProvider
       scoped_fake_statistics_provider_;
-  TestCallbackWaiterWithCounter waiter_{0};
+  std::unique_ptr<reporting::test::TestCallbackWaiter> waiter_;
 };
 
 // Make a log upload request. Have serialization and log upload succeed. Verify
@@ -273,7 +234,6 @@
   CompleteSerializeAndUpload(true /* success */);
   ExpectExtensionLogUploadSuccess();
   uploader_->RequestUpload();
-  waiter_.Wait();
 }
 
 // Make a log upload request. Have serialization succeed and log upload begin.
@@ -284,7 +244,9 @@
   reporting::MockReportQueue::EnqueueCallback upload_callback;
   CompleteSerializeAndCaptureUpload(&upload_callback);
   uploader_->RequestUpload();
-  waiter_.WaitAndReset();
+
+  WaitAndReset();
+
   Mock::VerifyAndClearExpectations(&delegate_);
 
   EXPECT_CALL(delegate_, SerializeExtensionLogForUpload_(_)).Times(0);
@@ -294,7 +256,6 @@
   ExpectExtensionLogUploadSuccess();
   EXPECT_CALL(delegate_, SerializeExtensionLogForUpload_(_)).Times(0);
   std::move(upload_callback).Run(reporting::Status::StatusOK());
-  waiter_.Wait();
 }
 
 // Make a log upload request. Have serialization begin. Make a second upload
@@ -306,7 +267,9 @@
       serialization_callback;
   CaptureSerialize(&serialization_callback);
   uploader_->RequestUpload();
-  waiter_.WaitAndReset();
+
+  WaitAndReset();
+
   Mock::VerifyAndClearExpectations(&delegate_);
 
   EXPECT_CALL(delegate_, SerializeExtensionLogForUpload_(_)).Times(0);
@@ -316,7 +279,6 @@
   CompleteUpload(true /* success */);
   ExpectExtensionLogUploadSuccess();
   std::move(serialization_callback).Run(&log_);
-  waiter_.Wait();
 }
 
 // Make a log upload request. Have serialization begin. Cancel the request. Have
@@ -327,7 +289,9 @@
       serialization_callback;
   CaptureSerialize(&serialization_callback);
   uploader_->RequestUpload();
-  waiter_.WaitAndReset();
+
+  WaitAndReset();
+
   Mock::VerifyAndClearExpectations(&delegate_);
 
   uploader_->CancelUpload();
@@ -375,7 +339,8 @@
 
     // FastForward until upload attempts are complete.
     task_environment_.FastForwardBy(expected_delay);
-    waiter_.WaitAndReset();
+
+    WaitAndReset();
 
     if (expected_delay == max_delay) {
       ++max_delay_count;
@@ -391,7 +356,9 @@
   ExpectExtensionLogUploadSuccess();
 
   task_environment_.FastForwardBy(expected_delay);
-  waiter_.WaitAndReset();
+
+  WaitAndReset();
+
   Mock::VerifyAndClearExpectations(&delegate_);
   Mock::VerifyAndClearExpectations(mock_report_queue_);
 
@@ -423,8 +390,8 @@
   CompleteSerializeAndUpload(true /* success */);
   ExpectExtensionLogUploadSuccess();
   uploader_->RequestUpload();
-  waiter_.Wait();
 
+  WaitAndReset();
   EXPECT_EQ(2u,
             value_report_
                 .FindListKey(RealtimeReportingJobConfiguration::kEventListKey)
diff --git a/chrome/browser/chromeos/policy/lock_to_single_user_manager.cc b/chrome/browser/chromeos/policy/lock_to_single_user_manager.cc
index 075e9fc..56938e12 100644
--- a/chrome/browser/chromeos/policy/lock_to_single_user_manager.cc
+++ b/chrome/browser/chromeos/policy/lock_to_single_user_manager.cc
@@ -36,26 +36,6 @@
   return chromeos::DBusThreadManager::Get()->GetConciergeClient();
 }
 
-// The result of locking the device to single user.
-// These values are logged to UMA. Entries should not be renumbered and
-// numeric values should never be reused. Please keep in sync with
-// "LockToSingleUserResult" in src/tools/metrics/histograms/enums.xml.
-enum class LockToSingleUserResult {
-  // Successfully locked to single user.
-  kSuccess = 0,
-  // No response from DBus call.
-  kNoResponse = 1,
-  // Request failed on Chrome OS side.
-  kFailedToLock = 2,
-  // Expected device to already be locked to a single user
-  kUnexpectedLockState = 3,
-  kMaxValue = kUnexpectedLockState,
-};
-
-void RecordDBusResult(LockToSingleUserResult result) {
-  base::UmaHistogramEnumeration("Enterprise.LockToSingleUserResult", result);
-}
-
 LockToSingleUserManager* g_lock_to_single_user_manager_instance;
 }  // namespace
 
@@ -168,7 +148,6 @@
   if (!reply || !reply->HasExtension(RebootOnSignOutReply::reply)) {
     LOG(ERROR) << "Signing out user: no reply from "
                   "LockToSingleUserMountUntilReboot D-Bus call.";
-    RecordDBusResult(LockToSingleUserResult::kNoResponse);
     chrome::AttemptUserExit();
     return;
   }
@@ -182,17 +161,9 @@
     // The device is locked to single user on TPM level. Update the cache in
     // SessionTerminationManager, so that it triggers reboot on sign out.
     chromeos::SessionTerminationManager::Get()->SetDeviceLockedToSingleUser();
-
-    if (expect_to_be_locked_ &&
-        extension.result() != RebootOnSignOutResult::PCR_ALREADY_EXTENDED) {
-      RecordDBusResult(LockToSingleUserResult::kUnexpectedLockState);
-    } else {
-      RecordDBusResult(LockToSingleUserResult::kSuccess);
-    }
   } else {
     LOG(ERROR) << "Signing out user: failed to lock device to single user: "
                << extension.result();
-    RecordDBusResult(LockToSingleUserResult::kFailedToLock);
     chrome::AttemptUserExit();
   }
 }
diff --git a/chrome/browser/chromeos/policy/policy_certs_browsertest.cc b/chrome/browser/chromeos/policy/policy_certs_browsertest.cc
index d46fd39aa..86a1119 100644
--- a/chrome/browser/chromeos/policy/policy_certs_browsertest.cc
+++ b/chrome/browser/chromeos/policy/policy_certs_browsertest.cc
@@ -470,7 +470,7 @@
       out_system_slot_available, std::move(done_closure)));
 }
 
-void IsCertInNSSDatabaseOnIOThread(content::ResourceContext* resource_context,
+void IsCertInNSSDatabaseOnIOThread(NssCertDatabaseGetter database_getter,
                                    const std::string& subject_common_name,
                                    bool* out_cert_found,
                                    base::OnceClosure done_closure) {
@@ -479,8 +479,8 @@
       &IsCertInNSSDatabaseOnIOThreadWithCertDb, subject_common_name,
       out_cert_found, base::AdaptCallbackForRepeating(std::move(done_closure)));
 
-  net::NSSCertDatabase* cert_db = GetNSSCertDatabaseForResourceContext(
-      resource_context, did_get_cert_db_callback);
+  net::NSSCertDatabase* cert_db =
+      std::move(database_getter).Run(did_get_cert_db_callback);
   if (cert_db)
     did_get_cert_db_callback.Run(cert_db);
 }
@@ -495,7 +495,7 @@
   content::GetIOThreadTaskRunner({})->PostTask(
       FROM_HERE,
       base::BindOnce(IsCertInNSSDatabaseOnIOThread,
-                     profile->GetResourceContext(), subject_common_name,
+                     CreateNSSCertDatabaseGetter(profile), subject_common_name,
                      &cert_found, run_loop.QuitClosure()));
   run_loop.Run();
   return cert_found;
diff --git a/chrome/browser/chromeos/policy/user_affiliation_browsertest.cc b/chrome/browser/chromeos/policy/user_affiliation_browsertest.cc
index 352ced0..61f5be4 100644
--- a/chrome/browser/chromeos/policy/user_affiliation_browsertest.cc
+++ b/chrome/browser/chromeos/policy/user_affiliation_browsertest.cc
@@ -75,18 +75,17 @@
   std::move(done_closure).Run();
 }
 
-void CheckIsSystemSlotAvailableOnIOThread(
-    content::ResourceContext* resource_context,
-    bool* out_system_slot_available,
-    base::OnceClosure done_closure) {
+void CheckIsSystemSlotAvailableOnIOThread(NssCertDatabaseGetter database_getter,
+                                          bool* out_system_slot_available,
+                                          base::OnceClosure done_closure) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   auto did_get_cert_db_callback = base::BindRepeating(
       &CheckIsSystemSlotAvailableOnIOThreadWithCertDb,
       out_system_slot_available,
       base::AdaptCallbackForRepeating(std::move(done_closure)));
 
-  net::NSSCertDatabase* cert_db = GetNSSCertDatabaseForResourceContext(
-      resource_context, did_get_cert_db_callback);
+  net::NSSCertDatabase* cert_db =
+      std::move(database_getter).Run(did_get_cert_db_callback);
   if (cert_db)
     did_get_cert_db_callback.Run(cert_db);
 }
@@ -102,8 +101,8 @@
   content::GetIOThreadTaskRunner({})->PostTask(
       FROM_HERE,
       base::BindOnce(CheckIsSystemSlotAvailableOnIOThread,
-                     profile->GetResourceContext(), &system_slot_available,
-                     run_loop.QuitClosure()));
+                     CreateNSSCertDatabaseGetter(profile),
+                     &system_slot_available, run_loop.QuitClosure()));
   run_loop.Run();
   return system_slot_available;
 }
diff --git a/chrome/browser/chromeos/printing/cups_proxy_service_delegate_impl.cc b/chrome/browser/chromeos/printing/cups_proxy_service_delegate_impl.cc
index 68d7e27d..c701acd 100644
--- a/chrome/browser/chromeos/printing/cups_proxy_service_delegate_impl.cc
+++ b/chrome/browser/chromeos/printing/cups_proxy_service_delegate_impl.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/bind_post_task.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_pref_names.h"
 #include "chrome/browser/chromeos/printing/cups_printers_manager.h"
 #include "chrome/browser/chromeos/printing/cups_printers_manager_factory.h"
@@ -85,15 +86,14 @@
   auto cb_runner = base::SequencedTaskRunnerHandle::Get();
   content::GetUIThreadTaskRunner({})->PostTask(
       FROM_HERE,
-      base::BindOnce(&CupsProxyServiceDelegateImpl::SetupPrinterOnThread,
+      base::BindOnce(&CupsProxyServiceDelegateImpl::SetupPrinterOnUIThread,
                      weak_factory_.GetWeakPtr(), printer,
-                     base::Passed(&cb_runner), std::move(cb)));
+                     base::BindPostTask(std::move(cb_runner), std::move(cb))));
 }
 
 // Runs on UI thread.
-void CupsProxyServiceDelegateImpl::SetupPrinterOnThread(
+void CupsProxyServiceDelegateImpl::SetupPrinterOnUIThread(
     const Printer& printer,
-    scoped_refptr<base::SequencedTaskRunner> cb_runner,
     cups_proxy::SetupPrinterCallback cb) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -104,19 +104,14 @@
 
   printer_configurer_->SetUpPrinter(
       printer, base::BindOnce(&CupsProxyServiceDelegateImpl::OnSetupPrinter,
-                              weak_factory_.GetWeakPtr(),
-                              base::Passed(&cb_runner), std::move(cb)));
+                              weak_factory_.GetWeakPtr(), std::move(cb)));
 }
 
-// |printer_configurer| unused but ensures this callback outlives it.
 void CupsProxyServiceDelegateImpl::OnSetupPrinter(
-    scoped_refptr<base::SequencedTaskRunner> cb_runner,
     cups_proxy::SetupPrinterCallback cb,
     PrinterSetupResult result) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  cb_runner->PostTask(
-      FROM_HERE,
-      base::BindOnce(std::move(cb), result == PrinterSetupResult::kSuccess));
+  std::move(cb).Run(result == PrinterSetupResult::kSuccess);
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/cups_proxy_service_delegate_impl.h b/chrome/browser/chromeos/printing/cups_proxy_service_delegate_impl.h
index 8d546b63..f2073bc0 100644
--- a/chrome/browser/chromeos/printing/cups_proxy_service_delegate_impl.h
+++ b/chrome/browser/chromeos/printing/cups_proxy_service_delegate_impl.h
@@ -60,11 +60,9 @@
 
  private:
   // Conducts SetupPrinter call on UI thread.
-  void SetupPrinterOnThread(const Printer& printer,
-                            scoped_refptr<base::SequencedTaskRunner> cb_runner,
-                            cups_proxy::SetupPrinterCallback cb);
-  void OnSetupPrinter(scoped_refptr<base::SequencedTaskRunner> cb_runner,
-                      cups_proxy::SetupPrinterCallback cb,
+  void SetupPrinterOnUIThread(const Printer& printer,
+                              cups_proxy::SetupPrinterCallback cb);
+  void OnSetupPrinter(cups_proxy::SetupPrinterCallback cb,
                       PrinterSetupResult result);
 
   // Current/active Profile. Not owned.
diff --git a/chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_network_context.cc b/chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_network_context.cc
index 0e6e70c15..af37bfb6 100644
--- a/chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_network_context.cc
+++ b/chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_network_context.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/data_use_measurement/chrome_data_use_measurement.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/network_service_instance.h"
@@ -124,6 +125,17 @@
   std::move(callback).Run();
 }
 
+void WilcoDtcSupportdNetworkContextImpl::OnDataUseUpdate(
+    int32_t network_traffic_annotation_id_hash,
+    int64_t recv_bytes,
+    int64_t sent_bytes) {
+  if (auto* data_use =
+          data_use_measurement::ChromeDataUseMeasurement::GetInstance()) {
+    data_use->ReportNetworkServiceDataUse(network_traffic_annotation_id_hash,
+                                          recv_bytes, sent_bytes);
+  }
+}
+
 void WilcoDtcSupportdNetworkContextImpl::Clone(
     mojo::PendingReceiver<network::mojom::URLLoaderNetworkServiceObserver>
         observer) {
diff --git a/chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_network_context.h b/chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_network_context.h
index 763385a4..3c5b2c24 100644
--- a/chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_network_context.h
+++ b/chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_network_context.h
@@ -76,6 +76,9 @@
                        OnClearSiteDataCallback callback) override;
   void OnLoadingStateUpdate(network::mojom::LoadInfoPtr info,
                             OnLoadingStateUpdateCallback callback) override;
+  void OnDataUseUpdate(int32_t network_traffic_annotation_id_hash,
+                       int64_t recv_bytes,
+                       int64_t sent_bytes) override;
   void Clone(
       mojo::PendingReceiver<network::mojom::URLLoaderNetworkServiceObserver>
           listener) override;
diff --git a/chrome/browser/commerce/subscriptions/android/BUILD.gn b/chrome/browser/commerce/subscriptions/android/BUILD.gn
new file mode 100644
index 0000000..def39df1
--- /dev/null
+++ b/chrome/browser/commerce/subscriptions/android/BUILD.gn
@@ -0,0 +1,24 @@
+# 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/android/rules.gni")
+
+android_library("java") {
+  sources = [
+    "java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscription.java",
+    "java/src/org/chromium/chrome/browser/subscriptions/ImplicitPriceDropSubscriptionsManager.java",
+    "java/src/org/chromium/chrome/browser/subscriptions/SubscriptionsManager.java",
+    "java/src/org/chromium/chrome/browser/subscriptions/SubscriptionsManagerImpl.java",
+  ]
+
+  deps = [
+    "//base:base_java",
+    "//chrome/android:base_module_java",
+    "//chrome/browser/flags:java",
+    "//chrome/browser/tab:java",
+    "//chrome/browser/tabmodel:java",
+    "//third_party/androidx:androidx_annotation_annotation_java",
+    "//url:gurl_java",
+  ]
+}
diff --git a/chrome/browser/commerce/subscriptions/android/DEPS b/chrome/browser/commerce/subscriptions/android/DEPS
new file mode 100644
index 0000000..ff761be
--- /dev/null
+++ b/chrome/browser/commerce/subscriptions/android/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+chrome/android/java/src/org/chromium/chrome/browser",
+]
\ No newline at end of file
diff --git a/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscription.java b/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscription.java
new file mode 100644
index 0000000..3c682db3
--- /dev/null
+++ b/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscription.java
@@ -0,0 +1,68 @@
+// 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.
+
+package org.chromium.chrome.browser.subscriptions;
+
+import androidx.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents the information for one commerce subscription entry.
+ */
+public class CommerceSubscription {
+    @StringDef({CommerceSubscriptionType.UNKNOWN, CommerceSubscriptionType.PRICE_TRACK})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CommerceSubscriptionType {
+        String UNKNOWN = "UNKNOWN";
+        String PRICE_TRACK = "PRICE_TRACK";
+    }
+    @StringDef({SubscriptionManagementType.UNKNOWN, SubscriptionManagementType.CHROME_MANAGED,
+            SubscriptionManagementType.USER_MANAGED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SubscriptionManagementType {
+        String UNKNOWN = "UNKNOWN";
+        String CHROME_MANAGED = "CHROME_MANAGED";
+        String USER_MANAGED = "USER_MANAGED";
+    }
+
+    private static final Long UNSAVED_SUBSCRIPTION = -1L;
+
+    private final Long mTimestamp;
+    private final @CommerceSubscriptionType String mType;
+    private final String mTrackingId;
+    private final @SubscriptionManagementType String mManagementType;
+
+    CommerceSubscription(@CommerceSubscriptionType String type, String trackingId,
+            @SubscriptionManagementType String managementType) {
+        this(type, trackingId, managementType, UNSAVED_SUBSCRIPTION);
+    }
+
+    CommerceSubscription(@CommerceSubscriptionType String type, String trackingId,
+            @SubscriptionManagementType String managementType, Long timestamp) {
+        mTrackingId = trackingId;
+        mType = type;
+        mManagementType = managementType;
+        mTimestamp = timestamp;
+    }
+
+    Long getTimestamp() {
+        return mTimestamp;
+    }
+
+    @CommerceSubscriptionType
+    String getType() {
+        return mType;
+    }
+
+    String getTrackingId() {
+        return mTrackingId;
+    }
+
+    @SubscriptionManagementType
+    String getManagementType() {
+        return mManagementType;
+    }
+}
diff --git a/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/ImplicitPriceDropSubscriptionsManager.java b/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/ImplicitPriceDropSubscriptionsManager.java
new file mode 100644
index 0000000..9d18ae7
--- /dev/null
+++ b/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/ImplicitPriceDropSubscriptionsManager.java
@@ -0,0 +1,119 @@
+// 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.
+
+package org.chromium.chrome.browser.subscriptions;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.chrome.browser.DeferredStartupHandler;
+import org.chromium.chrome.browser.subscriptions.CommerceSubscription.CommerceSubscriptionType;
+import org.chromium.chrome.browser.subscriptions.CommerceSubscription.SubscriptionManagementType;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.state.CriticalPersistedTabData;
+import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The class that manages Chrome-managed price drop subscriptions.
+ */
+public class ImplicitPriceDropSubscriptionsManager {
+    private final TabModelSelector mTabModelSelector;
+    private final TabModelObserver mTabModelObserver;
+    private final SubscriptionsManagerImpl mSubscriptionManager;
+
+    public ImplicitPriceDropSubscriptionsManager(
+            TabModelSelector tabModelSelector, SubscriptionsManagerImpl subscriptionsManager) {
+        mSubscriptionManager = subscriptionsManager;
+        mTabModelSelector = tabModelSelector;
+        mTabModelObserver = new TabModelObserver() {
+            @Override
+            public void tabClosureCommitted(Tab tab) {
+                unsubscribe(tab);
+            }
+
+            @Override
+            public void tabRemoved(Tab tab) {
+                unsubscribe(tab);
+            }
+        };
+        mTabModelSelector.getModel(false).addObserver(mTabModelObserver);
+        DeferredStartupHandler.getInstance().addDeferredTask(this::initializeSubscriptions);
+    }
+
+    private boolean isUniqueTab(Tab tab) {
+        TabModel normalTabModel = mTabModelSelector.getModel(false);
+        for (int index = 0; index < normalTabModel.getCount(); index++) {
+            Tab currentTab = normalTabModel.getTabAt(index);
+            if (currentTab.getId() == tab.getId()) {
+                continue;
+            }
+            if (currentTab.getOriginalUrl().getSpec().equals(tab.getOriginalUrl().getSpec())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Initialize the chrome-managed subscriptions.
+     */
+    @VisibleForTesting
+    void initializeSubscriptions() {
+        Map<String, Tab> urlTabMapping = new HashMap<>();
+        TabModel normalTabModel = mTabModelSelector.getModel(false);
+        for (int index = 0; index < normalTabModel.getCount(); index++) {
+            Tab tab = normalTabModel.getTabAt(index);
+            if (!hasOfferId(tab) || !isStaleTab(tab)) {
+                continue;
+            }
+            urlTabMapping.put(tab.getOriginalUrl().getSpec(), tab);
+        }
+        for (Tab tab : urlTabMapping.values()) {
+            CommerceSubscription subscription =
+                    new CommerceSubscription(CommerceSubscriptionType.PRICE_TRACK,
+                            ShoppingPersistedTabData.from(tab).getOfferId(),
+                            SubscriptionManagementType.CHROME_MANAGED);
+            mSubscriptionManager.subscribe(subscription);
+        }
+    }
+
+    private void unsubscribe(Tab tab) {
+        if (!isUniqueTab(tab)) {
+            return;
+        }
+
+        CommerceSubscription subscription =
+                new CommerceSubscription(CommerceSubscriptionType.PRICE_TRACK,
+                        ShoppingPersistedTabData.from(tab).getOfferId(),
+                        SubscriptionManagementType.CHROME_MANAGED);
+        mSubscriptionManager.unsubscribe(subscription);
+    }
+
+    private boolean hasOfferId(Tab tab) {
+        return !ShoppingPersistedTabData.from(tab).getOfferId().isEmpty();
+    }
+
+    // TODO(crbug.com/1186450): Extract this method to a utility class. Also, make the one-day time
+    // limit a field parameter.
+    private boolean isStaleTab(Tab tab) {
+        long tabLastOpenTime = System.currentTimeMillis()
+                - CriticalPersistedTabData.from(tab).getTimestampMillis();
+        return tabLastOpenTime <= TimeUnit.SECONDS.toMillis(
+                       ShoppingPersistedTabData.STALE_TAB_THRESHOLD_SECONDS.getValue())
+                && tabLastOpenTime >= TimeUnit.DAYS.toMillis(1);
+    }
+
+    /**
+     * Destroy any members that need clean up.
+     */
+    public void destroy() {
+        mTabModelSelector.getModel(false).removeObserver(mTabModelObserver);
+    }
+}
\ No newline at end of file
diff --git a/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/SubscriptionsManager.java b/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/SubscriptionsManager.java
new file mode 100644
index 0000000..d7f99b57
--- /dev/null
+++ b/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/SubscriptionsManager.java
@@ -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.
+
+package org.chromium.chrome.browser.subscriptions;
+
+/**
+ * Interface for exposing {@link CommerceSubscription} management.
+ */
+public interface SubscriptionsManager {
+    /**
+     * Creates a new subscription on the server if needed.
+     * @param subscription The {@link CommerceSubscription} to add.
+     */
+    void subscribe(CommerceSubscription subscription);
+
+    /**
+     * Destroys a subscription on the server if needed.
+     * @param subscription The {@link CommerceSubscription} to destroy.
+     */
+    void unsubscribe(CommerceSubscription subscription);
+
+    /**
+     * Returns all subscriptions that match the provided type.
+     * @param type The {@link CommerceSubscription.CommerceSubscriptionType} to query.
+     * @param forceFetch Whether to fetch from server.
+     */
+    void getSubscriptions(CommerceSubscription.CommerceSubscriptionType type, boolean forceFetch);
+}
\ No newline at end of file
diff --git a/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/SubscriptionsManagerImpl.java b/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/SubscriptionsManagerImpl.java
new file mode 100644
index 0000000..ee22d8e9
--- /dev/null
+++ b/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/SubscriptionsManagerImpl.java
@@ -0,0 +1,36 @@
+// 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.
+
+package org.chromium.chrome.browser.subscriptions;
+
+/**
+ * Implementation of {@link SubscriptionsManager} to manage price drop related subscriptions.
+ */
+public class SubscriptionsManagerImpl implements SubscriptionsManager {
+    public SubscriptionsManagerImpl() {}
+
+    /**
+     * Creates a new subscription on the server-side and refreshes the local storage of
+     * subscriptions.
+     * @param subscription The {@link CommerceSubscription} to add.
+     */
+    @Override
+    public void subscribe(CommerceSubscription subscription) {}
+
+    /**
+     * Destroys a subscription on the server-side and refreshes the local storage of subscriptions.
+     * @param subscription The {@link CommerceSubscription} to destroy.
+     */
+    @Override
+    public void unsubscribe(CommerceSubscription subscription) {}
+
+    /**
+     * Returns all subscriptions that match the provided type.
+     * @param type The {@link CommerceSubscription.CommerceSubscriptionType} to query.
+     * @param forceFetch Whether to fetch from server. If no, fetch from local storage.
+     */
+    @Override
+    public void getSubscriptions(
+            CommerceSubscription.CommerceSubscriptionType type, boolean forceFetch) {}
+}
\ No newline at end of file
diff --git a/chrome/browser/commerce/subscriptions/test/android/BUILD.gn b/chrome/browser/commerce/subscriptions/test/android/BUILD.gn
new file mode 100644
index 0000000..46eb758
--- /dev/null
+++ b/chrome/browser/commerce/subscriptions/test/android/BUILD.gn
@@ -0,0 +1,34 @@
+# 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/android/config.gni")
+import("//build/config/android/rules.gni")
+
+java_library("junit") {
+  # Skip platform checks since Robolectric depends on requires_android targets.
+  bypass_platform_checks = true
+
+  testonly = true
+
+  sources = [ "java/src/org/chromium/chrome/browser/subscriptions/ImplicitPriceDropSubscriptionsManagerUnitTest.java" ]
+
+  deps = [
+    "//base:base_java",
+    "//base:base_junit_test_support",
+    "//chrome/android:base_module_java",
+    "//chrome/android:chrome_java",
+    "//chrome/browser/commerce/subscriptions/android:java",
+    "//chrome/browser/flags:java",
+    "//chrome/browser/tab:java",
+    "//chrome/browser/tabmodel:java",
+    "//chrome/test/android:chrome_java_test_support",
+    "//third_party/android_deps:robolectric_all_java",
+    "//third_party/androidx:androidx_annotation_annotation_java",
+    "//third_party/androidx:androidx_test_runner_java",
+    "//third_party/hamcrest:hamcrest_core_java",
+    "//third_party/junit",
+    "//third_party/mockito:mockito_java",
+    "//url:gurl_java",
+  ]
+}
diff --git a/chrome/browser/commerce/subscriptions/test/android/java/src/org/chromium/chrome/browser/subscriptions/ImplicitPriceDropSubscriptionsManagerUnitTest.java b/chrome/browser/commerce/subscriptions/test/android/java/src/org/chromium/chrome/browser/subscriptions/ImplicitPriceDropSubscriptionsManagerUnitTest.java
new file mode 100644
index 0000000..e9b7ed8
--- /dev/null
+++ b/chrome/browser/commerce/subscriptions/test/android/java/src/org/chromium/chrome/browser/subscriptions/ImplicitPriceDropSubscriptionsManagerUnitTest.java
@@ -0,0 +1,240 @@
+// 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.
+
+package org.chromium.chrome.browser.subscriptions;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.UserDataHost;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.DeferredStartupHandler;
+import org.chromium.chrome.browser.tab.TabImpl;
+import org.chromium.chrome.browser.tab.state.CriticalPersistedTabData;
+import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.url.GURL;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link ImplicitPriceDropSubscriptionsManager}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class ImplicitPriceDropSubscriptionsManagerUnitTest {
+    @Rule
+    public TestRule mProcessor = new Features.JUnitProcessor();
+
+    private static final int TAB1_ID = 456;
+    private static final int TAB2_ID = 789;
+    private static final String URL1 = "www.foo.com";
+    private static final String URL2 = "www.bar.com";
+    private static final int POSITION1 = 0;
+    private static final int POSITION2 = 1;
+    private static final String OFFER1_ID = "offer_foo";
+    private static final String OFFER2_ID = "offer_bar";
+
+    @Mock
+    TabModel mTabModel;
+    @Mock
+    TabModelSelector mTabModelSelector;
+    @Mock
+    SubscriptionsManagerImpl mSubscriptionsManager;
+    @Mock
+    DeferredStartupHandler mDeferredStartupHandler;
+    @Mock
+    CriticalPersistedTabData mCriticalPersistedTabData1;
+    @Mock
+    CriticalPersistedTabData mCriticalPersistedTabData2;
+    @Mock
+    ShoppingPersistedTabData mShoppingPersistedTabData1;
+    @Mock
+    ShoppingPersistedTabData mShoppingPersistedTabData2;
+    @Captor
+    ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
+    @Captor
+    ArgumentCaptor<CommerceSubscription> mSubscriptionCaptor;
+
+    private TabImpl mTab1;
+    private TabImpl mTab2;
+    private ImplicitPriceDropSubscriptionsManager mImplicitSubscriptionsManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTab1 = prepareTab(
+                TAB1_ID, URL1, POSITION1, mCriticalPersistedTabData1, mShoppingPersistedTabData1);
+        mTab2 = prepareTab(
+                TAB2_ID, URL2, POSITION2, mCriticalPersistedTabData2, mShoppingPersistedTabData2);
+        // Mock that tab1 and tab2 both have offer ID and are stale tabs.
+        doReturn(OFFER1_ID).when(mShoppingPersistedTabData1).getOfferId();
+        doReturn(OFFER2_ID).when(mShoppingPersistedTabData2).getOfferId();
+        long fakeTimestamp = System.currentTimeMillis()
+                - TimeUnit.SECONDS.toMillis(
+                        ShoppingPersistedTabData.STALE_TAB_THRESHOLD_SECONDS.getValue())
+                + TimeUnit.DAYS.toMillis(7);
+        doReturn(fakeTimestamp).when(mCriticalPersistedTabData1).getTimestampMillis();
+        doReturn(fakeTimestamp).when(mCriticalPersistedTabData2).getTimestampMillis();
+        doReturn(2).when(mTabModel).getCount();
+        doReturn(mTabModel).when(mTabModelSelector).getModel(false);
+        doNothing().when(mTabModel).addObserver(mTabModelObserverCaptor.capture());
+        DeferredStartupHandler.setInstanceForTests(mDeferredStartupHandler);
+
+        mImplicitSubscriptionsManager =
+                new ImplicitPriceDropSubscriptionsManager(mTabModelSelector, mSubscriptionsManager);
+    }
+
+    @After
+    public void tearDown() {
+        DeferredStartupHandler.setInstanceForTests(null);
+    }
+
+    @Test
+    public void testInitialSetup() {
+        verify(mTabModel).addObserver(any(TabModelObserver.class));
+        verify(mDeferredStartupHandler).addDeferredTask(any(Runnable.class));
+    }
+
+    @Test
+    public void testInitialSubscription_AllUnique() {
+        doReturn(2).when(mTabModel).getCount();
+
+        mImplicitSubscriptionsManager.initializeSubscriptions();
+
+        verify(mSubscriptionsManager, times(2)).subscribe(mSubscriptionCaptor.capture());
+        assertThat(mSubscriptionCaptor.getAllValues().get(0).getTrackingId(),
+                equalTo(String.valueOf(OFFER1_ID)));
+        assertThat(mSubscriptionCaptor.getAllValues().get(1).getTrackingId(),
+                equalTo(String.valueOf(OFFER2_ID)));
+    }
+
+    @Test
+    public void testInitialSubscription_WithDuplicateURL() {
+        mTab1 = prepareTab(
+                TAB1_ID, URL1, POSITION1, mCriticalPersistedTabData1, mShoppingPersistedTabData1);
+        mTab2 = prepareTab(
+                TAB2_ID, URL1, POSITION2, mCriticalPersistedTabData2, mShoppingPersistedTabData2);
+
+        mImplicitSubscriptionsManager.initializeSubscriptions();
+
+        verify(mSubscriptionsManager, times(1)).subscribe(mSubscriptionCaptor.capture());
+        assertThat(mSubscriptionCaptor.getAllValues().get(0).getTrackingId(),
+                equalTo(String.valueOf(OFFER2_ID)));
+    }
+
+    @Test
+    public void testInitialSubscription_NoOfferID() {
+        doReturn("").when(mShoppingPersistedTabData1).getOfferId();
+
+        mImplicitSubscriptionsManager.initializeSubscriptions();
+
+        verify(mSubscriptionsManager, times(1)).subscribe(mSubscriptionCaptor.capture());
+        assertThat(mSubscriptionCaptor.getAllValues().get(0).getTrackingId(),
+                equalTo(String.valueOf(OFFER2_ID)));
+    }
+
+    @Test
+    public void testInitialSubscription_TabTooOld() {
+        doReturn(System.currentTimeMillis()
+                - TimeUnit.SECONDS.toMillis(
+                        ShoppingPersistedTabData.STALE_TAB_THRESHOLD_SECONDS.getValue())
+                - TimeUnit.DAYS.toMillis(7))
+                .when(mCriticalPersistedTabData1)
+                .getTimestampMillis();
+
+        mImplicitSubscriptionsManager.initializeSubscriptions();
+
+        verify(mSubscriptionsManager, times(1)).subscribe(mSubscriptionCaptor.capture());
+        assertThat(mSubscriptionCaptor.getAllValues().get(0).getTrackingId(),
+                equalTo(String.valueOf(OFFER2_ID)));
+    }
+
+    @Test
+    public void testInitialSubscription_TabTooNew() {
+        doReturn(System.currentTimeMillis()).when(mCriticalPersistedTabData1).getTimestampMillis();
+
+        mImplicitSubscriptionsManager.initializeSubscriptions();
+
+        verify(mSubscriptionsManager, times(1)).subscribe(mSubscriptionCaptor.capture());
+        assertThat(mSubscriptionCaptor.getAllValues().get(0).getTrackingId(),
+                equalTo(String.valueOf(OFFER2_ID)));
+    }
+
+    @Test
+    public void testTabClosure() {
+        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
+
+        verify(mSubscriptionsManager, times(1)).unsubscribe(mSubscriptionCaptor.capture());
+        assertThat(mSubscriptionCaptor.getAllValues().get(0).getTrackingId(),
+                equalTo(String.valueOf(OFFER1_ID)));
+    }
+
+    @Test
+    public void testTabRemove() {
+        mTabModelObserverCaptor.getValue().tabRemoved(mTab1);
+
+        verify(mSubscriptionsManager, times(1)).unsubscribe(mSubscriptionCaptor.capture());
+        assertThat(mSubscriptionCaptor.getAllValues().get(0).getTrackingId(),
+                equalTo(String.valueOf(OFFER1_ID)));
+    }
+
+    @Test
+    public void testUnsubscribe_NotUnique() {
+        mTab1 = prepareTab(
+                TAB1_ID, URL1, POSITION1, mCriticalPersistedTabData1, mShoppingPersistedTabData1);
+        mTab2 = prepareTab(
+                TAB2_ID, URL1, POSITION2, mCriticalPersistedTabData2, mShoppingPersistedTabData2);
+
+        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
+
+        verify(mSubscriptionsManager, never()).unsubscribe(mSubscriptionCaptor.capture());
+    }
+
+    @Test
+    public void testDestroy() {
+        mImplicitSubscriptionsManager.destroy();
+
+        verify(mTabModel).removeObserver(any(TabModelObserver.class));
+    }
+
+    private TabImpl prepareTab(int id, String urlString, int position,
+            CriticalPersistedTabData criticalPersistedTabData,
+            ShoppingPersistedTabData shoppingPersistedTabData) {
+        TabImpl tab = mock(TabImpl.class);
+        doReturn(id).when(tab).getId();
+        doReturn(urlString).when(tab).getUrlString();
+        GURL gurl = mock(GURL.class);
+        doReturn(urlString).when(gurl).getSpec();
+        doReturn(gurl).when(tab).getOriginalUrl();
+        doReturn(tab).when(mTabModel).getTabAt(position);
+        UserDataHost userDataHost = new UserDataHost();
+        userDataHost.setUserData(CriticalPersistedTabData.class, criticalPersistedTabData);
+        userDataHost.setUserData(ShoppingPersistedTabData.class, shoppingPersistedTabData);
+        doReturn(userDataHost).when(tab).getUserDataHost();
+        return tab;
+    }
+}
diff --git a/chrome/browser/component_updater/chrome_component_updater_configurator.cc b/chrome/browser/component_updater/chrome_component_updater_configurator.cc
index 56ad563a..3a40a4a 100644
--- a/chrome/browser/component_updater/chrome_component_updater_configurator.cc
+++ b/chrome/browser/component_updater/chrome_component_updater_configurator.cc
@@ -141,7 +141,7 @@
 }
 
 std::string ChromeConfigurator::GetChannel() const {
-  return chrome::GetChannelName();
+  return chrome::GetChannelName(chrome::WithExtendedStable(true));
 }
 
 std::string ChromeConfigurator::GetBrand() const {
diff --git a/chrome/browser/diagnostics/recon_diagnostics.cc b/chrome/browser/diagnostics/recon_diagnostics.cc
index a5fb242..36c5b93d 100644
--- a/chrome/browser/diagnostics/recon_diagnostics.cc
+++ b/chrome/browser/diagnostics/recon_diagnostics.cc
@@ -294,7 +294,8 @@
       RecordFailure(DIAG_RECON_EMPTY_VERSION, "Empty Version");
       return true;
     }
-    std::string version_modifier = chrome::GetChannelName();
+    std::string version_modifier =
+        chrome::GetChannelName(chrome::WithExtendedStable(true));
     if (!version_modifier.empty())
       current_version += " " + version_modifier;
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
diff --git a/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.cc b/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.cc
index 7dd047b..806c878e4 100644
--- a/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.cc
+++ b/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.cc
@@ -16,6 +16,7 @@
 #include "content/public/browser/download_item_utils.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/storage_partition.h"
+#include "google_apis/gaia/google_service_auth_error.h"
 #include "google_apis/gaia/oauth2_access_token_consumer.h"
 #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
 #include "google_apis/gaia/oauth2_api_call_flow.h"
@@ -57,8 +58,10 @@
   SetHasWindowSizeControls(true);
   SetTitle(IDS_PROFILES_GAIA_SIGNIN_TITLE);
   SetButtons(ui::DIALOG_BUTTON_NONE);
-  // TODO(https://crbug.com/1158490): Add a way to cancel user login.
   set_use_custom_frame(false);
+  SetCancelCallback(
+      base::BindOnce(&FileSystemSigninDialogDelegate::OnCancellation,
+                     weak_factory_.GetWeakPtr()));
 
   AddChildView(web_view_.get());
   SetLayoutManager(std::make_unique<views::FillLayout>());
@@ -143,6 +146,12 @@
   return static_cast<views::View*>(web_view_.get());
 }
 
+void FileSystemSigninDialogDelegate::OnCancellation() {
+  std::move(callback_).Run(
+      GoogleServiceAuthError{GoogleServiceAuthError::State::REQUEST_CANCELED},
+      std::string(), std::string());
+}
+
 void FileSystemSigninDialogDelegate::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
   const GURL& url = navigation_handle->GetURL();
@@ -162,8 +171,9 @@
       content::BrowserContext::GetStoragePartitionForSite(
           web_view_->GetBrowserContext(), GURL(kFileSystemBoxEndpointApi));
   auto url_loader = partition->GetURLLoaderFactoryForBrowserProcess();
-  auto callback = base::BindOnce(
-      &FileSystemSigninDialogDelegate::OnGotOAuthTokens, factory_.GetWeakPtr());
+  auto callback =
+      base::BindOnce(&FileSystemSigninDialogDelegate::OnGotOAuthTokens,
+                     weak_factory_.GetWeakPtr());
 
   // No refresh_token, so need to get both tokens with authorization code.
   token_fetcher_ = std::make_unique<AccessTokenFetcher>(
diff --git a/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.h b/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.h
index 2634ccf..f1811e29 100644
--- a/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.h
+++ b/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.h
@@ -65,6 +65,8 @@
   void DeleteDelegate() override;
   views::View* GetInitiallyFocusedView() override;
 
+  void OnCancellation();
+
   // content::WebContentsObserver:
   void DidFinishNavigation(
       content::NavigationHandle* navigation_handle) override;
@@ -77,7 +79,7 @@
   std::unique_ptr<views::WebView> web_view_;
   std::unique_ptr<OAuth2AccessTokenFetcherImpl> token_fetcher_;
   AuthorizationCompletedCallback callback_;
-  base::WeakPtrFactory<FileSystemSigninDialogDelegate> factory_{this};
+  base::WeakPtrFactory<FileSystemSigninDialogDelegate> weak_factory_{this};
 };
 
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/extensions/api/identity/web_auth_flow.cc b/chrome/browser/extensions/api/identity/web_auth_flow.cc
index e3ff426d..d017f92 100644
--- a/chrome/browser/extensions/api/identity/web_auth_flow.cc
+++ b/chrome/browser/extensions/api/identity/web_auth_flow.cc
@@ -153,7 +153,8 @@
   // This has to mirror the logic in WebViewGuest::CreateWebContents for
   // creating the correct StoragePartitionConfig.
   auto result = content::StoragePartitionConfig::Create(
-      extension_misc::kIdentityApiUiAppId, GetPartitionName(partition),
+      browser_context, extension_misc::kIdentityApiUiAppId,
+      GetPartitionName(partition),
       /*in_memory=*/true);
   result.set_fallback_to_partition_domain_for_blob_urls(
       browser_context->IsOffTheRecord()
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc b/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc
index cf60bd7..0d9f40c 100644
--- a/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc
+++ b/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc
@@ -35,18 +35,6 @@
 
 namespace web_navigation = api::web_navigation;
 
-namespace {
-
-using TabObserverMap =
-    std::map<content::WebContents*, WebNavigationTabObserver*>;
-
-TabObserverMap& GetTabObserverMap() {
-  static base::NoDestructor<TabObserverMap> s;
-  return *s;
-}
-
-}  // namespace
-
 // WebNavigtionEventRouter -------------------------------------------
 
 WebNavigationEventRouter::PendingWebContents::PendingWebContents() = default;
@@ -181,7 +169,6 @@
 WebNavigationTabObserver::WebNavigationTabObserver(
     content::WebContents* web_contents)
     : WebContentsObserver(web_contents) {
-  GetTabObserverMap().insert(TabObserverMap::value_type(web_contents, this));
   navigation_state_.FrameHostCreated(web_contents->GetMainFrame());
 }
 
@@ -190,8 +177,7 @@
 // static
 WebNavigationTabObserver* WebNavigationTabObserver::Get(
     content::WebContents* web_contents) {
-  auto i = GetTabObserverMap().find(web_contents);
-  return i == GetTabObserverMap().end() ? NULL : i->second;
+  return FromWebContents(web_contents);
 }
 
 void WebNavigationTabObserver::RenderFrameDeleted(
@@ -367,10 +353,6 @@
       !new_contents_is_present_in_tabstrip);
 }
 
-void WebNavigationTabObserver::WebContentsDestroyed() {
-  GetTabObserverMap().erase(web_contents());
-}
-
 void WebNavigationTabObserver::DispatchCachedOnBeforeNavigate() {
   if (!pending_on_before_navigate_event_)
     return;
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_api.h b/chrome/browser/extensions/api/web_navigation/web_navigation_api.h
index 497cab0d..22a632d4 100644
--- a/chrome/browser/extensions/api/web_navigation/web_navigation_api.h
+++ b/chrome/browser/extensions/api/web_navigation/web_navigation_api.h
@@ -65,7 +65,6 @@
                            ui::PageTransition transition,
                            bool started_from_context_menu,
                            bool renderer_initiated) override;
-  void WebContentsDestroyed() override;
 
   // This method dispatches the already created onBeforeNavigate event.
   void DispatchCachedOnBeforeNavigate();
diff --git a/chrome/browser/extensions/extension_browsertest_browsertest.cc b/chrome/browser/extensions/extension_browsertest_browsertest.cc
index 26f7928..91c1af4 100644
--- a/chrome/browser/extensions/extension_browsertest_browsertest.cc
+++ b/chrome/browser/extensions/extension_browsertest_browsertest.cc
@@ -6,13 +6,11 @@
 
 #include "base/time/time.h"
 #include "chrome/browser/extensions/unpacked_installer.h"
-#include "components/version_info/channel.h"
 #include "content/public/test/browser_test.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_host_queue.h"
 #include "extensions/browser/test_extension_registry_observer.h"
 #include "extensions/common/extension.h"
-#include "extensions/common/features/feature_channel.h"
 #include "extensions/test/test_extension_dir.h"
 
 namespace extensions {
@@ -51,7 +49,6 @@
 
   const char* background_key = nullptr;
   int manifest_version = 2;
-  base::Optional<ScopedCurrentChannel> channel_override;
   switch (GetParam()) {
     case BackgroundType::kPersistentPage:
       background_key = kPersistentBackgroundPage;
@@ -61,9 +58,6 @@
       break;
     case BackgroundType::kWorker:
       background_key = kWorkerBackground;
-      // Worker-based background scripts are channel-restricted, and available
-      // in manifest v3.
-      channel_override.emplace(version_info::Channel::UNKNOWN);
       manifest_version = 3;
       break;
   }
diff --git a/chrome/browser/extensions/extension_install_ui_browsertest.cc b/chrome/browser/extensions/extension_install_ui_browsertest.cc
index 71e27ac..03cbdff8 100644
--- a/chrome/browser/extensions/extension_install_ui_browsertest.cc
+++ b/chrome/browser/extensions/extension_install_ui_browsertest.cc
@@ -7,11 +7,11 @@
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/browser.h"
@@ -85,11 +85,9 @@
   }
 
   void WaitForThemeChange() {
-    content::WindowedNotificationObserver theme_change_observer(
-        chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-        content::Source<ThemeService>(
-            ThemeServiceFactory::GetForProfile(browser()->profile())));
-    theme_change_observer.Wait();
+    test::ThemeServiceChangedWaiter waiter(
+        ThemeServiceFactory::GetForProfile(browser()->profile()));
+    waiter.WaitForThemeChanged();
   }
 
  private:
diff --git a/chrome/browser/extensions/extension_service_sync_unittest.cc b/chrome/browser/extensions/extension_service_sync_unittest.cc
index 47c59885..96fb724 100644
--- a/chrome/browser/extensions/extension_service_sync_unittest.cc
+++ b/chrome/browser/extensions/extension_service_sync_unittest.cc
@@ -15,7 +15,6 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/stl_util.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
 #include "chrome/browser/extensions/component_loader.h"
 #include "chrome/browser/extensions/extension_service.h"
@@ -27,6 +26,7 @@
 #include "chrome/browser/extensions/updater/extension_updater.h"
 #include "chrome/browser/profiles/profile_key.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
+#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/common/chrome_constants.h"
@@ -1727,12 +1727,10 @@
 
   // Installing a theme should not result in a sync change (themes are handled
   // separately by ThemeSyncableService).
+  test::ThemeServiceChangedWaiter waiter(
+      ThemeServiceFactory::GetForProfile(profile()));
   InstallCRX(data_dir().AppendASCII("theme.crx"), INSTALL_NEW);
-  content::WindowedNotificationObserver theme_change_observer(
-      chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-      content::Source<ThemeService>(
-          ThemeServiceFactory::GetForProfile(profile())));
-  theme_change_observer.Wait();
+  waiter.WaitForThemeChanged();
   EXPECT_TRUE(processor->changes().empty());
 }
 
diff --git a/chrome/browser/extensions/theme_installed_infobar_delegate.cc b/chrome/browser/extensions/theme_installed_infobar_delegate.cc
index f539add..535efecd1 100644
--- a/chrome/browser/extensions/theme_installed_infobar_delegate.cc
+++ b/chrome/browser/extensions/theme_installed_infobar_delegate.cc
@@ -11,7 +11,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/app/vector_icons/vector_icons.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/themes/theme_service.h"
@@ -19,7 +18,6 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/infobars/core/infobar.h"
-#include "content/public/browser/notification_source.h"
 #include "extensions/browser/extension_system.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -70,13 +68,12 @@
       theme_name_(theme_name),
       theme_id_(theme_id),
       prev_theme_reinstaller_(std::move(prev_theme_reinstaller)) {
-  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-                 content::Source<ThemeService>(theme_service_));
+  theme_service_->AddObserver(this);
 }
 
 ThemeInstalledInfoBarDelegate::~ThemeInstalledInfoBarDelegate() {
   // We don't want any notifications while we're running our destructor.
-  registrar_.RemoveAll();
+  theme_service_->RemoveObserver(this);
 }
 
 infobars::InfoBarDelegate::InfoBarIdentifier
@@ -114,11 +111,7 @@
   return false;  // The theme change will close us.
 }
 
-void ThemeInstalledInfoBarDelegate::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  DCHECK_EQ(chrome::NOTIFICATION_BROWSER_THEME_CHANGED, type);
+void ThemeInstalledInfoBarDelegate::OnThemeChanged() {
   // If the new theme is different from what this info bar is associated with,
   // close this info bar since it is no longer relevant.
   if (theme_id_ != theme_service_->GetThemeID())
diff --git a/chrome/browser/extensions/theme_installed_infobar_delegate.h b/chrome/browser/extensions/theme_installed_infobar_delegate.h
index 036deb3..c2f6a4f 100644
--- a/chrome/browser/extensions/theme_installed_infobar_delegate.h
+++ b/chrome/browser/extensions/theme_installed_infobar_delegate.h
@@ -10,9 +10,8 @@
 #include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "chrome/browser/themes/theme_service.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "components/infobars/core/confirm_infobar_delegate.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
 #include "extensions/common/extension_id.h"
 #include "third_party/skia/include/core/SkColor.h"
 
@@ -21,7 +20,7 @@
 // When a user installs a theme, we display it immediately, but provide an
 // infobar allowing them to cancel.
 class ThemeInstalledInfoBarDelegate : public ConfirmInfoBarDelegate,
-                                      public content::NotificationObserver {
+                                      public ThemeServiceObserver {
  public:
   // Creates a theme installed infobar and delegate and adds the infobar to
   // |infobar_service|, replacing any previous theme infobar.
@@ -49,10 +48,8 @@
   base::string16 GetButtonLabel(InfoBarButton button) const override;
   bool Cancel() override;
 
-  // content::NotificationObserver:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  // ThemeServiceObserver:
+  void OnThemeChanged() override;
 
   ThemeService* theme_service_;
 
@@ -64,9 +61,6 @@
 
   // Used to undo theme install.
   std::unique_ptr<ThemeService::ThemeReinstaller> prev_theme_reinstaller_;
-
-  // Registers and unregisters us for notifications.
-  content::NotificationRegistrar registrar_;
 };
 
 #endif  // CHROME_BROWSER_EXTENSIONS_THEME_INSTALLED_INFOBAR_DELEGATE_H_
diff --git a/chrome/browser/extensions/updater/chrome_update_client_config.cc b/chrome/browser/extensions/updater/chrome_update_client_config.cc
index 80eafc5..217fd6f 100644
--- a/chrome/browser/extensions/updater/chrome_update_client_config.cc
+++ b/chrome/browser/extensions/updater/chrome_update_client_config.cc
@@ -179,7 +179,7 @@
 }
 
 std::string ChromeUpdateClientConfig::GetChannel() const {
-  return chrome::GetChannelName();
+  return chrome::GetChannelName(chrome::WithExtendedStable(true));
 }
 
 std::string ChromeUpdateClientConfig::GetBrand() const {
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index cabb9010..50d9e7f 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3122,6 +3122,11 @@
     "expiry_milestone": 90
   },
   {
+    "name": "help-app-launcher-search",
+    "owners": [ "//chromeos/components/help_app_ui/OWNERS" ],
+    "expiry_milestone": 93
+  },
+  {
     "name": "help-app-search-service-integration",
     "owners": [ "//chromeos/components/help_app_ui/OWNERS" ],
     "expiry_milestone": 90
@@ -4204,7 +4209,7 @@
     "expiry_milestone": 100
   },
   {
-    "name": "preemptive-link-to-text-generation",
+    "name": "preemtive-link-to-text-generation",
     "owners": [ "cheickcisse@google.com" ],
     "expiry_milestone": 92
   },
@@ -4947,16 +4952,6 @@
     "expiry_milestone": 82
   },
   {
-    "name": "tabbed-app-overflow-menu-icons",
-    "owners": [ "gangwu" ],
-    "expiry_milestone": 90
-  },
-  {
-    "name": "tabbed-app-overflow-menu-regroup",
-    "owners": [ "gangwu" ],
-    "expiry_milestone": 90
-  },
-  {
     "name": "tabbed-app-overflow-menu-three-button-actionbar",
     "owners": [ "gangwu" ],
     "expiry_milestone": 91
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index c5942b3..d220d0c9 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2643,9 +2643,9 @@
     "Improvements to Shared Highlighting. Including ability to reshare or "
     "remove a highlight.";
 
-const char kPreemptiveLinkToTextGenerationName[] =
+const char kPreemtiveLinkToTextGenerationName[] =
     "Preemptive generation of link to text";
-const char kPreemptiveLinkToTextGenerationDescription[] =
+const char kPreemtiveLinkToTextGenerationDescription[] =
     "Enables link to text to be generated in advance.";
 
 const char kDraw1PredictedPoint12Ms[] = "1 point 12ms ahead.";
@@ -3398,16 +3398,6 @@
     "Allows user to use touch gestures to move the text cursor around. This "
     "flag will only take effect on Android 11 and above.";
 
-const char kTabbedAppOverflowMenuIconsName[] =
-    "Android tabbed app overflow menu icons";
-const char kTabbedAppOverflowMenuIconsDescription[] =
-    "If enabled, shows icon in front of each overflow menu item.";
-
-const char kTabbedAppOverflowMenuRegroupName[] =
-    "Android tabbed app overflow menu Regroup";
-const char kTabbedAppOverflowMenuRegroupDescription[] =
-    "If enabled, regroup overflow menu items.";
-
 const char kTabbedAppOverflowMenuThreeButtonActionbarName[] =
     "Android tabbed app overflow menu three buttons actionbar";
 const char kTabbedAppOverflowMenuThreeButtonActionbarDescription[] =
@@ -4457,6 +4447,10 @@
 const char kFullRestoreName[] = "Full restore";
 const char kFullRestoreDescription[] = "Chrome OS full restore";
 
+const char kHelpAppLauncherSearchName[] = "Help App launcher search";
+const char kHelpAppLauncherSearchDescription[] =
+    "Enables showing search results from the help app in the launcher.";
+
 const char kHelpAppSearchServiceIntegrationName[] =
     "Help App search service integration";
 const char kHelpAppSearchServiceIntegrationDescription[] =
@@ -4547,7 +4541,9 @@
 
 const char kLacrosSupportName[] = "Lacros support";
 const char kLacrosSupportDescription[] =
-    "Support for the experimental lacros-chrome browser.";
+    "Support for the experimental lacros-chrome browser. This disables "
+    "the existing Chrome OS multiple sign-in. Please use Lacros multi-profile "
+    "instead.";
 
 const char kLacrosWebAppsName[] = " Lacros web apps";
 const char kLacrosWebAppsDescription[] = "Support web apps in Lacros browser.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 7bb799ca..1be824f 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1546,8 +1546,8 @@
 extern const char kSharedHighlightingV2Name[];
 extern const char kSharedHighlightingV2Description[];
 
-extern const char kPreemptiveLinkToTextGenerationName[];
-extern const char kPreemptiveLinkToTextGenerationDescription[];
+extern const char kPreemtiveLinkToTextGenerationName[];
+extern const char kPreemtiveLinkToTextGenerationDescription[];
 
 extern const char kDraw1PredictedPoint12Ms[];
 extern const char kDraw2PredictedPoints6Ms[];
@@ -1970,12 +1970,6 @@
 extern const char kSwipeToMoveCursorName[];
 extern const char kSwipeToMoveCursorDescription[];
 
-extern const char kTabbedAppOverflowMenuIconsName[];
-extern const char kTabbedAppOverflowMenuIconsDescription[];
-
-extern const char kTabbedAppOverflowMenuRegroupName[];
-extern const char kTabbedAppOverflowMenuRegroupDescription[];
-
 extern const char kTabbedAppOverflowMenuThreeButtonActionbarName[];
 extern const char kTabbedAppOverflowMenuThreeButtonActionbarDescription[];
 
@@ -2616,6 +2610,9 @@
 extern const char kFullRestoreName[];
 extern const char kFullRestoreDescription[];
 
+extern const char kHelpAppLauncherSearchName[];
+extern const char kHelpAppLauncherSearchDescription[];
+
 extern const char kHelpAppSearchServiceIntegrationName[];
 extern const char kHelpAppSearchServiceIntegrationDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 1aeabac3..8132d2f6 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -44,7 +44,6 @@
 #include "components/reading_list/features/reading_list_switches.h"
 #include "components/safe_browsing/core/features.h"
 #include "components/security_state/core/features.h"
-#include "components/shared_highlighting/core/common/features.h"
 #include "components/signin/public/base/account_consistency_method.h"
 #include "components/signin/public/base/signin_switches.h"
 #include "components/subresource_filter/core/browser/subresource_filter_features.h"
@@ -104,7 +103,7 @@
     &features::kMetricsSettingsAndroid,
     &features::kNetworkServiceInProcess,
     &features::kPredictivePrefetchingAllowedOnAllConnectionTypes,
-    &features::kPreemptiveLinkToTextGeneration,
+    &features::kPreemtiveLinkToTextGeneration,
     &features::kPrivacyReorderedAndroid,
     &features::kPrivacySandboxSettings,
     &features::kPrioritizeBootstrapTasks,
@@ -230,8 +229,6 @@
     &kTabReparenting,
     &kTabSwitcherOnReturn,
     &kTabToGTSAnimation,
-    &kTabbedAppOverflowMenuIcons,
-    &kTabbedAppOverflowMenuRegroup,
     &kTabbedAppOverflowMenuThreeButtonActionbar,
     &kTestDefaultDisabled,
     &kTestDefaultEnabled,
@@ -647,12 +644,6 @@
 const base::Feature kTabToGTSAnimation{"TabToGTSAnimation",
                                        base::FEATURE_ENABLED_BY_DEFAULT};
 
-const base::Feature kTabbedAppOverflowMenuIcons{
-    "TabbedAppOverflowMenuIcons", base::FEATURE_ENABLED_BY_DEFAULT};
-
-const base::Feature kTabbedAppOverflowMenuRegroup{
-    "TabbedAppOverflowMenuRegroup", base::FEATURE_ENABLED_BY_DEFAULT};
-
 const base::Feature kTabbedAppOverflowMenuThreeButtonActionbar{
     "TabbedAppOverflowMenuThreeButtonActionbar",
     base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 99fc913..872d9fb 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -122,8 +122,6 @@
 extern const base::Feature kTabReparenting;
 extern const base::Feature kTabSwitcherOnReturn;
 extern const base::Feature kTabToGTSAnimation;
-extern const base::Feature kTabbedAppOverflowMenuIcons;
-extern const base::Feature kTabbedAppOverflowMenuRegroup;
 extern const base::Feature kTabbedAppOverflowMenuThreeButtonActionbar;
 extern const base::Feature kTestDefaultDisabled;
 extern const base::Feature kTestDefaultEnabled;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
index cd62ec7..3825a8b 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
@@ -78,8 +78,6 @@
             put(ChromeFeatureList.TEST_DEFAULT_ENABLED, true);
             put(ChromeFeatureList.REPORT_FEED_USER_ACTIONS, false);
             put(ChromeFeatureList.INTEREST_FEED_V2, true);
-            put(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_ICONS, false);
-            put(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP, false);
             put(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR, false);
             put(ChromeFeatureList.THEME_REFACTOR_ANDROID, false);
             put(ChromeFeatureList.USE_CHIME_ANDROID_SDK, false);
@@ -383,11 +381,11 @@
     }
 
     private static String getPrefForFeatureFlag(String featureName) {
-        String grandfatheredPrefKey = sNonDynamicPrefKeys.get(featureName);
-        if (grandfatheredPrefKey == null) {
+        String legacyPrefKey = sNonDynamicPrefKeys.get(featureName);
+        if (legacyPrefKey == null) {
             return ChromePreferenceKeys.FLAGS_CACHED.createKey(featureName);
         } else {
-            return grandfatheredPrefKey;
+            return legacyPrefKey;
         }
     }
 
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 9d3a1b08f..5893e94 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -389,8 +389,7 @@
     public static final String PHOTO_PICKER_VIDEO_SUPPORT = "PhotoPickerVideoSupport";
     public static final String PORTALS = "Portals";
     public static final String PORTALS_CROSS_ORIGIN = "PortalsCrossOrigin";
-    public static final String PREEMPTIVE_LINK_TO_TEXT_GENERATION =
-            "PreemptiveLinkToTextGeneration";
+    public static final String PREEMTIVE_LINK_TO_TEXT_GENERATION = "PreemtiveLinkToTextGeneration";
     public static final String PREDICTIVE_PREFETCHING_ALLOWED_ON_ALL_CONNECTION_TYPES =
             "PredictivePrefetchingAllowedOnAllConnectionTypes";
     public static final String PREFETCH_NOTIFICATION_SCHEDULING_INTEGRATION =
@@ -451,8 +450,6 @@
     public static final String TAB_REPARENTING = "TabReparenting";
     public static final String TAB_SWITCHER_ON_RETURN = "TabSwitcherOnReturn";
     public static final String TAB_TO_GTS_ANIMATION = "TabToGTSAnimation";
-    public static final String TABBED_APP_OVERFLOW_MENU_ICONS = "TabbedAppOverflowMenuIcons";
-    public static final String TABBED_APP_OVERFLOW_MENU_REGROUP = "TabbedAppOverflowMenuRegroup";
     public static final String TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR =
             "TabbedAppOverflowMenuThreeButtonActionbar";
     public static final String TEST_DEFAULT_DISABLED = "TestDefaultDisabled";
diff --git a/chrome/browser/history/history_tab_helper.cc b/chrome/browser/history/history_tab_helper.cc
index a6695a59..ae84716a 100644
--- a/chrome/browser/history/history_tab_helper.cc
+++ b/chrome/browser/history/history_tab_helper.cc
@@ -10,6 +10,7 @@
 #include "base/stl_util.h"
 #include "build/build_config.h"
 #include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/history_clusters/history_clusters_tab_helper.h"
 #include "chrome/browser/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/history/content/browser/history_context_helper.h"
@@ -189,7 +190,19 @@
     return;
 #endif
 
+  HistoryClustersTabHelper* clusters_tab_helper =
+      HistoryClustersTabHelper::FromWebContents(web_contents());
+  if (clusters_tab_helper) {
+    clusters_tab_helper->WillUpdateHistoryForNavigation(navigation_handle,
+                                                        add_page_args);
+  }
+
   UpdateHistoryForNavigation(add_page_args);
+
+  if (clusters_tab_helper) {
+    clusters_tab_helper->DidUpdateHistoryForNavigation(navigation_handle,
+                                                       add_page_args);
+  }
 }
 
 // We update history upon the associated WebContents becoming the top level
diff --git a/chrome/browser/history_clusters/history_clusters_tab_helper.cc b/chrome/browser/history_clusters/history_clusters_tab_helper.cc
new file mode 100644
index 0000000..405da00
--- /dev/null
+++ b/chrome/browser/history_clusters/history_clusters_tab_helper.cc
@@ -0,0 +1,25 @@
+// 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/history_clusters/history_clusters_tab_helper.h"
+
+#include "components/history/core/browser/history_types.h"
+
+HistoryClustersTabHelper::HistoryClustersTabHelper(
+    content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents) {}
+
+HistoryClustersTabHelper::~HistoryClustersTabHelper() = default;
+
+void HistoryClustersTabHelper::WillUpdateHistoryForNavigation(
+    content::NavigationHandle* navigation_handle,
+    const history::HistoryAddPageArgs& add_page_args) {}
+
+void HistoryClustersTabHelper::DidUpdateHistoryForNavigation(
+    content::NavigationHandle* navigation_handle,
+    const history::HistoryAddPageArgs& add_page_args) {}
+
+void HistoryClustersTabHelper::WebContentsDestroyed() {}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(HistoryClustersTabHelper)
diff --git a/chrome/browser/history_clusters/history_clusters_tab_helper.h b/chrome/browser/history_clusters/history_clusters_tab_helper.h
new file mode 100644
index 0000000..ea15f82
--- /dev/null
+++ b/chrome/browser/history_clusters/history_clusters_tab_helper.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 CHROME_BROWSER_HISTORY_CLUSTERS_HISTORY_CLUSTERS_TAB_HELPER_H_
+#define CHROME_BROWSER_HISTORY_CLUSTERS_HISTORY_CLUSTERS_TAB_HELPER_H_
+
+#include "base/optional.h"
+#include "components/memories/core/visit_data.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace history {
+struct HistoryAddPageArgs;
+}
+
+namespace memories {
+class MemoriesService;
+}
+
+class HistoryClustersTabHelper
+    : public content::WebContentsObserver,
+      public content::WebContentsUserData<HistoryClustersTabHelper> {
+ public:
+  ~HistoryClustersTabHelper() override;
+
+  HistoryClustersTabHelper(const HistoryClustersTabHelper&) = delete;
+  HistoryClustersTabHelper& operator=(const HistoryClustersTabHelper&) = delete;
+
+  // Called by HistoryTabHelper right before, and after, submitting a new
+  // navigation for |web_contents()| to HistoryService. We need close
+  // coordination with History's conception of the visit lifetime.
+  void WillUpdateHistoryForNavigation(
+      content::NavigationHandle* navigation_handle,
+      const history::HistoryAddPageArgs& add_page_args);
+  void DidUpdateHistoryForNavigation(
+      content::NavigationHandle* navigation_handle,
+      const history::HistoryAddPageArgs& add_page_args);
+
+ private:
+  explicit HistoryClustersTabHelper(content::WebContents* web_contents);
+  friend class content::WebContentsUserData<HistoryClustersTabHelper>;
+
+  // content::WebContentsObserver implementation.
+  void WebContentsDestroyed() override;
+
+  // Helper function to return the Memories service.  May return nullptr.
+  memories::MemoriesService* GetMemoriesService();
+
+  // Clustering signals for the currently active visit, if it exists.
+  base::Optional<memories::MemoriesVisit> current_visit_data_;
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+};
+
+#endif  // CHROME_BROWSER_HISTORY_CLUSTERS_HISTORY_CLUSTERS_TAB_HELPER_H_
diff --git a/chrome/browser/media/webrtc/display_media_access_handler.cc b/chrome/browser/media/webrtc/display_media_access_handler.cc
index dd1fc23..73e1390 100644
--- a/chrome/browser/media/webrtc/display_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/display_media_access_handler.cc
@@ -318,11 +318,14 @@
       const auto& visible_url = url_formatter::FormatUrlForSecurityDisplay(
           web_contents->GetLastCommittedURL(),
           url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC);
+      const bool disable_local_echo =
+          (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS) &&
+          media_id.web_contents_id.disable_local_echo;
       ui = GetDevicesForDesktopCapture(
           web_contents, &devices, media_id, pending_request.request.video_type,
           blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE,
-          media_id.audio_share, false /* disable_local_echo */,
-          display_notification_, visible_url, visible_url);
+          media_id.audio_share, disable_local_echo, display_notification_,
+          visible_url, visible_url);
     }
   }
 
diff --git a/chrome/browser/metrics/authenticator_utility.cc b/chrome/browser/metrics/authenticator_utility.cc
index 4a40f87c1..0b697c1 100644
--- a/chrome/browser/metrics/authenticator_utility.cc
+++ b/chrome/browser/metrics/authenticator_utility.cc
@@ -20,11 +20,6 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/is_uvpaa.h"
 
-#if defined(OS_ANDROID)
-#include "base/android/jni_android.h"
-#include "chrome/android/chrome_jni_headers/IsUvpaaHelper_jni.h"
-#endif
-
 #if defined(OS_MAC)
 #include "device/fido/mac/authenticator.h"
 #endif
@@ -79,9 +74,12 @@
                      ChromeAuthenticatorRequestDelegate::
                          TouchIdAuthenticatorConfigForProfile(profile)));
 }
-#endif  // defined(OS_MAC)
+#endif
 
 void ReportUVPlatformAuthenticatorAvailability() {
+  // This only reports metrics for desktop platforms. For mobile devices, the
+  // platform version is an exact proxy for whether a platform authenticator
+  // can be used.
 #if defined(OS_MAC)
   DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
   // IsUVPAA() is prone to crashes/hangs on macOS. Downsample metric collection
@@ -102,24 +100,7 @@
       base::BindOnce(&ReportAvailability));
 #elif BUILDFLAG(IS_CHROMEOS_ASH)
   // TODO(crbug.com/1181426): Reenable the IsUVPAA() startup metric on CrOS.
-#elif defined(OS_ANDROID)
-  JNIEnv* env = base::android::AttachCurrentThread();
-
-  ::webauth::Java_IsUvpaaHelper_isUserVerifyingPlatformAuthenticatorAvailable(
-      env);
 #endif
 }
 
 }  // namespace authenticator_utility
-
-#if defined(OS_ANDROID)
-
-namespace webauth {
-
-void JNI_IsUvpaaHelper_OnIsUvpaaComplete(JNIEnv* env, jboolean available) {
-  authenticator_utility::ReportAvailability(static_cast<bool>(available));
-}
-
-}  // namespace webauth
-
-#endif  // defined(OS_ANDROID)
diff --git a/chrome/browser/metrics/power/power_metrics_reporter.cc b/chrome/browser/metrics/power/power_metrics_reporter.cc
index 155ee445..7732127 100644
--- a/chrome/browser/metrics/power/power_metrics_reporter.cc
+++ b/chrome/browser/metrics/power/power_metrics_reporter.cc
@@ -170,11 +170,6 @@
   auto usage_metrics = data_store_->ResetIntervalData();
   auto source_id = usage_metrics.source_id_for_longest_visible_origin;
 
-  // TODO(sebmarchand): Figure out if we need to report data when we don't have
-  // a valid sourceID.
-  if (source_id == ukm::kInvalidSourceId)
-    return;
-
   ukm::builders::PowerUsageScenariosIntervalData builder(source_id);
   builder.SetURLVisibilityTimeSeconds(ukm::GetExponentialBucketMinForUserTiming(
       usage_metrics.source_id_for_longest_visible_origin_duration.InSeconds()));
diff --git a/chrome/browser/metrics/power/power_metrics_reporter_unittest.cc b/chrome/browser/metrics/power/power_metrics_reporter_unittest.cc
index c4429be..0623631 100644
--- a/chrome/browser/metrics/power/power_metrics_reporter_unittest.cc
+++ b/chrome/browser/metrics/power/power_metrics_reporter_unittest.cc
@@ -16,6 +16,7 @@
 #include "components/ukm/test_ukm_recorder.h"
 #include "services/metrics/public/cpp/metrics_utils.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
@@ -201,6 +202,8 @@
       ukm::builders::PowerUsageScenariosIntervalData::kEntryName);
   EXPECT_EQ(1u, entries.size());
 
+  EXPECT_EQ(entries[0]->source_id,
+            fake_interval_data.source_id_for_longest_visible_origin);
   test_ukm_recorder_.ExpectEntryMetric(
       entries[0], UkmEntry::kUptimeSecondsName,
       ukm::GetExponentialBucketMinForUserTiming(
@@ -309,6 +312,7 @@
       ukm::builders::PowerUsageScenariosIntervalData::kEntryName);
   EXPECT_EQ(1u, entries.size());
 
+  EXPECT_EQ(entries[0]->source_id, 42);
   test_ukm_recorder_.ExpectEntryMetric(
       entries[0], UkmEntry::kBrowserShuttingDownName, true);
 }
@@ -518,3 +522,33 @@
       kBatteryDischargeModeHistogramName,
       PowerMetricsReporterAccess::BatteryDischargeMode::kDischarging, 1);
 }
+
+TEST_F(PowerMetricsReporterUnitTest, UKMsNoTab) {
+  UsageScenarioDataStore::IntervalData fake_interval_data;
+
+  fake_interval_data.max_tab_count = 0;
+  fake_interval_data.max_visible_window_count = 0;
+  fake_interval_data.source_id_for_longest_visible_origin =
+      ukm::kInvalidSourceId;
+
+  task_environment_.FastForwardBy(kExpectedMetricsCollectionInterval);
+  battery_states_.push(BatteryLevelProvider::BatteryState{
+      1, 1, 0.50, true, base::TimeTicks::Now()});
+
+  data_store_.SetIntervalDataToReturn(fake_interval_data);
+
+  performance_monitor::ProcessMonitor::Metrics fake_metrics = {};
+  fake_metrics.cpu_usage = 0.5;
+
+  WaitForNextSample(fake_metrics);
+
+  auto entries = test_ukm_recorder_.GetEntriesByName(
+      ukm::builders::PowerUsageScenariosIntervalData::kEntryName);
+  EXPECT_EQ(1u, entries.size());
+
+  EXPECT_EQ(entries[0]->source_id, ukm::kInvalidSourceId);
+  test_ukm_recorder_.ExpectEntryMetric(
+      entries[0], UkmEntry::kUptimeSecondsName,
+      ukm::GetExponentialBucketMinForUserTiming(
+          fake_interval_data.uptime_at_interval_end.InSeconds()));
+}
diff --git a/chrome/browser/nearby_sharing/nearby_notification_manager.cc b/chrome/browser/nearby_sharing/nearby_notification_manager.cc
index 9e8d254..955eff8c 100644
--- a/chrome/browser/nearby_sharing/nearby_notification_manager.cc
+++ b/chrome/browser/nearby_sharing/nearby_notification_manager.cc
@@ -581,6 +581,7 @@
   DCHECK(notification_display_service_);
   DCHECK(nearby_service_);
   DCHECK(pref_service_);
+  nearby_service_->AddObserver(this);
   nearby_service_->RegisterReceiveSurface(
       this, NearbySharingService::ReceiveSurfaceState::kBackground);
   nearby_service_->RegisterSendSurface(
@@ -588,6 +589,7 @@
 }
 
 NearbyNotificationManager::~NearbyNotificationManager() {
+  nearby_service_->RemoveObserver(this);
   nearby_service_->UnregisterReceiveSurface(this);
   nearby_service_->UnregisterSendSurface(this, this);
 }
@@ -655,6 +657,16 @@
   // Nothing to do here.
 }
 
+void NearbyNotificationManager::OnNearbyProcessStopped() {
+  if (share_target_ && last_transfer_status_) {
+    ShowFailure(
+        *share_target_,
+        TransferMetadataBuilder().set_status(*last_transfer_status_).build());
+  }
+  share_target_ = base::nullopt;
+  last_transfer_status_ = base::nullopt;
+}
+
 void NearbyNotificationManager::ShowProgress(
     const ShareTarget& share_target,
     const TransferMetadata& transfer_metadata) {
diff --git a/chrome/browser/nearby_sharing/nearby_notification_manager.h b/chrome/browser/nearby_sharing/nearby_notification_manager.h
index 8191bad..559fa7c 100644
--- a/chrome/browser/nearby_sharing/nearby_notification_manager.h
+++ b/chrome/browser/nearby_sharing/nearby_notification_manager.h
@@ -10,12 +10,13 @@
 #include "base/optional.h"
 #include "base/time/time.h"
 #include "chrome/browser/nearby_sharing/nearby_notification_delegate.h"
+#include "chrome/browser/nearby_sharing/nearby_sharing_service.h"
 #include "chrome/browser/nearby_sharing/share_target.h"
 #include "chrome/browser/nearby_sharing/share_target_discovered_callback.h"
 #include "chrome/browser/nearby_sharing/transfer_metadata.h"
+#include "chrome/browser/nearby_sharing/transfer_metadata_builder.h"
 #include "chrome/browser/nearby_sharing/transfer_update_callback.h"
 
-class NearbySharingService;
 class NotificationDisplayService;
 class PrefService;
 class Profile;
@@ -25,7 +26,8 @@
 // be shown as simultaneous connections are not supported. All methods should be
 // called from the UI thread.
 class NearbyNotificationManager : public TransferUpdateCallback,
-                                  public ShareTargetDiscoveredCallback {
+                                  public ShareTargetDiscoveredCallback,
+                                  public NearbySharingService::Observer {
  public:
   static constexpr base::TimeDelta kOnboardingDismissedTimeout =
       base::TimeDelta::FromMinutes(15);
@@ -61,6 +63,11 @@
   void OnShareTargetDiscovered(ShareTarget share_target) override;
   void OnShareTargetLost(ShareTarget share_target) override;
 
+  // NearbySharingService::Observer
+  void OnHighVisibilityChanged(bool in_high_visibility) override {}
+  void OnNearbyProcessStopped() override;
+  void OnShutdown() override {}
+
   // Shows a progress notification of the data being transferred to or from
   // |share_target|. Has a cancel action to cancel the transfer.
   void ShowProgress(const ShareTarget& share_target,
diff --git a/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc b/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc
index 5c90c0e..51387e2b 100644
--- a/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc
@@ -512,26 +512,36 @@
   for (FileAttachment::Type type : param.file_attachments)
     share_target.file_attachments.push_back(CreateFileAttachment(type));
 
-  for (std::pair<TransferMetadata::Status, int> error :
-       std::vector<std::pair<TransferMetadata::Status, int>>{
-           {TransferMetadata::Status::kNotEnoughSpace,
-            IDS_NEARBY_ERROR_NOT_ENOUGH_SPACE},
-           {TransferMetadata::Status::kTimedOut, IDS_NEARBY_ERROR_TIME_OUT},
-           {TransferMetadata::Status::kUnsupportedAttachmentType,
-            IDS_NEARBY_ERROR_UNSUPPORTED_FILE_TYPE},
-           {TransferMetadata::Status::kFailed, 0},
+  for (base::Optional<std::pair<TransferMetadata::Status, int>> error :
+       std::vector<base::Optional<std::pair<TransferMetadata::Status, int>>>{
+           std::make_pair(TransferMetadata::Status::kNotEnoughSpace,
+                          IDS_NEARBY_ERROR_NOT_ENOUGH_SPACE),
+           std::make_pair(TransferMetadata::Status::kTimedOut,
+                          IDS_NEARBY_ERROR_TIME_OUT),
+           std::make_pair(TransferMetadata::Status::kUnsupportedAttachmentType,
+                          IDS_NEARBY_ERROR_UNSUPPORTED_FILE_TYPE),
+           std::make_pair(TransferMetadata::Status::kFailed, 0),
+           base::nullopt,
        }) {
-    manager()->ShowFailure(
-        share_target,
-        TransferMetadataBuilder().set_status(error.first).build());
+    if (error) {
+      manager()->ShowFailure(
+          share_target,
+          TransferMetadataBuilder().set_status(error->first).build());
+    } else {
+      manager()->OnTransferUpdate(
+          share_target, TransferMetadataBuilder()
+                            .set_status(TransferMetadata::Status::kInProgress)
+                            .build());
+      manager()->OnNearbyProcessStopped();
+    }
 
     base::string16 expected_title = FormatNotificationTitle(
         is_incoming ? IDS_NEARBY_NOTIFICATION_RECEIVE_FAILURE_TITLE
                     : IDS_NEARBY_NOTIFICATION_SEND_FAILURE_TITLE,
         param, device_name, /*use_capitalized_resource=*/false);
     base::string16 expected_message =
-        error.second ? l10n_util::GetStringUTF16(error.second)
-                     : base::string16();
+        error && error->second ? l10n_util::GetStringUTF16(error->second)
+                               : base::string16();
 
     std::vector<message_center::Notification> notifications =
         GetDisplayedNotifications();
diff --git a/chrome/browser/net/network_context_configuration_browsertest.cc b/chrome/browser/net/network_context_configuration_browsertest.cc
index c0148de..2142033 100644
--- a/chrome/browser/net/network_context_configuration_browsertest.cc
+++ b/chrome/browser/net/network_context_configuration_browsertest.cc
@@ -313,9 +313,10 @@
   content::StoragePartition* GetStoragePartitionForContextType(
       NetworkContextType network_context_type) {
     const auto kOnDiskConfig = content::StoragePartitionConfig::Create(
-        "foo", /*partition_name=*/"", /*in_memory=*/false);
+        browser()->profile(), "foo", /*partition_name=*/"",
+        /*in_memory=*/false);
     const auto kInMemoryConfig = content::StoragePartitionConfig::Create(
-        "foo", /*partition_name=*/"", /*in_memory=*/true);
+        browser()->profile(), "foo", /*partition_name=*/"", /*in_memory=*/true);
 
     switch (network_context_type) {
       case NetworkContextType::kSystem:
@@ -335,10 +336,19 @@
       case NetworkContextType::kInMemoryApp:
         return content::BrowserContext::GetStoragePartition(
             browser()->profile(), kInMemoryConfig);
-      case NetworkContextType::kOnDiskAppWithIncognitoProfile:
+      case NetworkContextType::kOnDiskAppWithIncognitoProfile: {
         DCHECK(incognito_);
+        // Note: Even though we are requesting an on-disk config, the function
+        // will return an in-memory config because incognito profiles are not
+        // supposed to to use on-disk storage.
+        const auto kIncognitoConfig = content::StoragePartitionConfig::Create(
+            incognito_->profile(), "foo", /*partition_name=*/"",
+            /*in_memory=*/false);
+        DCHECK(kIncognitoConfig.in_memory());
+
         return content::BrowserContext::GetStoragePartition(
-            incognito_->profile(), kOnDiskConfig);
+            incognito_->profile(), kIncognitoConfig);
+      }
     }
     NOTREACHED();
     return nullptr;
diff --git a/chrome/browser/net/nss_context.cc b/chrome/browser/net/nss_context.cc
index 50382cc..e5305c9 100644
--- a/chrome/browser/net/nss_context.cc
+++ b/chrome/browser/net/nss_context.cc
@@ -11,7 +11,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/resource_context.h"
 
 using content::BrowserThread;
 
@@ -30,7 +29,7 @@
 
 // Gets NSSCertDatabase for the resource context.
 void GetCertDBOnIOThread(
-    content::ResourceContext* context,
+    NssCertDatabaseGetter database_getter,
     scoped_refptr<base::SequencedTaskRunner> response_task_runner,
     base::OnceCallback<void(net::NSSCertDatabase*)> callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
@@ -40,7 +39,7 @@
   auto completion_callback = base::AdaptCallbackForRepeating(base::BindOnce(
       &DidGetCertDBOnIOThread, response_task_runner, std::move(callback)));
   net::NSSCertDatabase* cert_db =
-      GetNSSCertDatabaseForResourceContext(context, completion_callback);
+      std::move(database_getter).Run(completion_callback);
 
   if (cert_db)
     completion_callback.Run(cert_db);
@@ -55,6 +54,6 @@
 
   content::GetIOThreadTaskRunner({})->PostTask(
       FROM_HERE,
-      base::BindOnce(&GetCertDBOnIOThread, profile->GetResourceContext(),
+      base::BindOnce(&GetCertDBOnIOThread, CreateNSSCertDatabaseGetter(profile),
                      base::ThreadTaskRunnerHandle::Get(), std::move(callback)));
 }
diff --git a/chrome/browser/net/nss_context.h b/chrome/browser/net/nss_context.h
index d981c20..1b8e918 100644
--- a/chrome/browser/net/nss_context.h
+++ b/chrome/browser/net/nss_context.h
@@ -20,19 +20,26 @@
 }
 
 namespace content {
+class BrowserContext;
 class ResourceContext;
 }  // namespace content
 
-// Returns a pointer to the NSSCertDatabase for the user associated with
-// |context|, if it is ready. If it is not ready and |callback| is non-null, the
-// |callback| will be run once the DB is initialized. Ownership is not
-// transferred, but the caller may save the pointer, which will remain valid for
-// the lifetime of the ResourceContext.
-// Must be called only on the IO thread.
-net::NSSCertDatabase* GetNSSCertDatabaseForResourceContext(
-    content::ResourceContext* context,
-    base::OnceCallback<void(net::NSSCertDatabase*)> callback)
-    WARN_UNUSED_RESULT;
+// NssCertDatabaseGetter is a callback that MUST only be invoked on the IO
+// thread, and will either synchronously return the associated NSSCertDatabase*
+// (if available), or nullptr along with a commitment to asynchronously invoke
+// the caller-supplied callback once the NSSCertDatabase* has been initialized.
+// Ownership of the NSSCertDatabase is not transferred, and the lifetime should
+// only be considered valid for the current Task.
+//
+// TODO(https://crbug.com/1186373): Provide better lifetime guarantees.
+using NssCertDatabaseGetter = base::OnceCallback<net::NSSCertDatabase*(
+    base::OnceCallback<void(net::NSSCertDatabase*)> callback)>;
+
+// Must be called on the UI thread. Returns a Getter that may only be invoked on
+// the IO thread. To avoid UAF, the getter must be immediately posted to the IO
+// thread and then invoked.
+NssCertDatabaseGetter CreateNSSCertDatabaseGetter(
+    content::BrowserContext* browser_context);
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 // Sets the |username_hash| associated with |context|.
diff --git a/chrome/browser/net/nss_context_chromeos.cc b/chrome/browser/net/nss_context_chromeos.cc
index a0d7c35..a5d05ca66 100644
--- a/chrome/browser/net/nss_context_chromeos.cc
+++ b/chrome/browser/net/nss_context_chromeos.cc
@@ -12,6 +12,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/supports_user_data.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/resource_context.h"
 #include "crypto/nss_util_internal.h"
@@ -130,8 +131,6 @@
     callback.Run(db);
 }
 
-}  // namespace
-
 net::NSSCertDatabase* GetNSSCertDatabaseForResourceContext(
     content::ResourceContext* context,
     base::OnceCallback<void(net::NSSCertDatabase*)> callback) {
@@ -139,6 +138,17 @@
       context, base::BindOnce(&CallWithNSSCertDatabase, std::move(callback)));
 }
 
+}  // namespace
+
+NssCertDatabaseGetter CreateNSSCertDatabaseGetter(
+    content::BrowserContext* browser_context) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(browser_context);
+  return base::BindOnce(
+      &GetNSSCertDatabaseForResourceContext,
+      base::Unretained(browser_context->GetResourceContext()));
+}
+
 void SetNSSCertDatabaseUsernameHash(content::ResourceContext* context,
                                     const std::string& username_hash) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
diff --git a/chrome/browser/net/nss_context_chromeos_browsertest.cc b/chrome/browser/net/nss_context_chromeos_browsertest.cc
index db82de3..1c7b632 100644
--- a/chrome/browser/net/nss_context_chromeos_browsertest.cc
+++ b/chrome/browser/net/nss_context_chromeos_browsertest.cc
@@ -39,10 +39,10 @@
   bool DoGetDBTests() {
     base::RunLoop run_loop;
     content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(&DBTester::GetDBAndDoTestsOnIOThread,
-                       base::Unretained(this), profile_->GetResourceContext(),
-                       run_loop.QuitClosure()));
+        FROM_HERE, base::BindOnce(&DBTester::GetDBAndDoTestsOnIOThread,
+                                  base::Unretained(this),
+                                  CreateNSSCertDatabaseGetter(profile_),
+                                  run_loop.QuitClosure()));
     run_loop.Run();
     return !!db_;
   }
@@ -51,10 +51,10 @@
   void DoGetDBAgainTests() {
     base::RunLoop run_loop;
     content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(&DBTester::DoGetDBAgainTestsOnIOThread,
-                       base::Unretained(this), profile_->GetResourceContext(),
-                       run_loop.QuitClosure()));
+        FROM_HERE, base::BindOnce(&DBTester::DoGetDBAgainTestsOnIOThread,
+                                  base::Unretained(this),
+                                  CreateNSSCertDatabaseGetter(profile_),
+                                  run_loop.QuitClosure()));
     run_loop.Run();
   }
 
@@ -66,10 +66,11 @@
   }
 
  private:
-  void GetDBAndDoTestsOnIOThread(content::ResourceContext* context,
+  void GetDBAndDoTestsOnIOThread(NssCertDatabaseGetter database_getter,
                                  const base::RepeatingClosure& done_callback) {
-    net::NSSCertDatabase* db = GetNSSCertDatabaseForResourceContext(
-        context, base::BindOnce(&DBTester::DoTestsOnIOThread,
+    net::NSSCertDatabase* db =
+        std::move(database_getter)
+            .Run(base::BindOnce(&DBTester::DoTestsOnIOThread,
                                 base::Unretained(this), done_callback));
     if (db) {
       DVLOG(1) << "got db synchronously";
@@ -93,10 +94,10 @@
                                                  std::move(done_callback));
   }
 
-  void DoGetDBAgainTestsOnIOThread(content::ResourceContext* context,
+  void DoGetDBAgainTestsOnIOThread(NssCertDatabaseGetter database_getter,
                                    base::OnceClosure done_callback) {
-    net::NSSCertDatabase* db = GetNSSCertDatabaseForResourceContext(
-        context, base::BindOnce(&NotCalledDbCallback));
+    net::NSSCertDatabase* db =
+        std::move(database_getter).Run(base::BindOnce(&NotCalledDbCallback));
     // Should always be synchronous now.
     EXPECT_TRUE(db);
     // Should return the same db as before.
diff --git a/chrome/browser/net/nss_context_linux.cc b/chrome/browser/net/nss_context_linux.cc
index c5ea31f7..54db021f 100644
--- a/chrome/browser/net/nss_context_linux.cc
+++ b/chrome/browser/net/nss_context_linux.cc
@@ -11,10 +11,8 @@
 
 namespace {
 net::NSSCertDatabase* g_nss_cert_database = NULL;
-}  // namespace
 
 net::NSSCertDatabase* GetNSSCertDatabaseForResourceContext(
-    content::ResourceContext* context,
     base::OnceCallback<void(net::NSSCertDatabase*)> callback) {
   // This initialization is not thread safe. This CHECK ensures that this code
   // is only run on a single thread.
@@ -37,3 +35,14 @@
   }
   return g_nss_cert_database;
 }
+
+}  // namespace
+
+NssCertDatabaseGetter CreateNSSCertDatabaseGetter(
+    content::BrowserContext* browser_context) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  // Not used, but should not be nullptr, since it's used by the ChromeOS Ash
+  // implementation of this method.
+  DCHECK(browser_context);
+  return base::BindOnce(&GetNSSCertDatabaseForResourceContext);
+}
diff --git a/chrome/browser/notifications/notification_alert_service_bridge_unittest.mm b/chrome/browser/notifications/notification_alert_service_bridge_unittest.mm
index ec8c2536..eb5fb077 100644
--- a/chrome/browser/notifications/notification_alert_service_bridge_unittest.mm
+++ b/chrome/browser/notifications/notification_alert_service_bridge_unittest.mm
@@ -10,6 +10,7 @@
 #include "base/process/process_handle.h"
 #include "base/run_loop.h"
 #include "base/strings/string16.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_callback.h"
 #import "chrome/browser/notifications/notification_alert_service_bridge.h"
 #include "chrome/services/mac_notifications/public/cpp/notification_constants_mac.h"
@@ -259,6 +260,7 @@
 }
 
 TEST_F(NotificationAlertServiceBridgeTest, OnNotificationAction) {
+  base::HistogramTester histogram_tester;
   auto profile_identifier = mac_notifications::mojom::ProfileIdentifier::New(
       "profileId", /*incognito=*/true);
   auto notification_identifier =
@@ -279,4 +281,8 @@
   // TODO(knollr): verify expected notification action data.
   // Wait until the action has been handled.
   run_loop.Run();
+
+  histogram_tester.ExpectUniqueSample(
+      "Notifications.macOS.ActionReceived.Alert", /*sample=*/true,
+      /*expected_count=*/1);
 }
diff --git a/chrome/browser/notifications/notification_platform_bridge_mac.mm b/chrome/browser/notifications/notification_platform_bridge_mac.mm
index 437477b..a7fa5f3 100644
--- a/chrome/browser/notifications/notification_platform_bridge_mac.mm
+++ b/chrome/browser/notifications/notification_platform_bridge_mac.mm
@@ -27,6 +27,7 @@
 #include "chrome/browser/notifications/mac_notification_provider_factory.h"
 #include "chrome/browser/notifications/notification_common.h"
 #include "chrome/browser/notifications/notification_display_service_impl.h"
+#include "chrome/browser/notifications/notification_platform_bridge_mac_metrics.h"
 #include "chrome/browser/notifications/notification_platform_bridge_mac_utils.h"
 #include "chrome/browser/notifications/platform_notification_service_impl.h"
 #include "chrome/browser/profiles/profile.h"
@@ -138,6 +139,7 @@
       notification_type != NotificationHandler::Type::EXTENSION;
 
   bool is_alert = IsAlertNotificationMac(notification);
+  LogMacNotificationDelivered(is_alert, /*sucess=*/true);
 
   [builder setSubTitle:base::SysUTF16ToNSString(CreateMacNotificationContext(
                            is_alert, notification, requires_attribution))];
diff --git a/chrome/browser/notifications/notification_platform_bridge_mac_metrics.cc b/chrome/browser/notifications/notification_platform_bridge_mac_metrics.cc
new file mode 100644
index 0000000..3dc40b2
--- /dev/null
+++ b/chrome/browser/notifications/notification_platform_bridge_mac_metrics.cc
@@ -0,0 +1,31 @@
+// 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/notifications/notification_platform_bridge_mac_metrics.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
+
+namespace {
+
+// Returns a suffix to be used in UMA histogram names. Needs to be kept in sync
+// with token entries of Notifications.macOS.{ActionReceived,Delivered} metrics
+// in //tools/metrics/histograms/histograms_xml/notifications/histograms.xml.
+std::string NotificationStyleSuffix(bool is_alert) {
+  return is_alert ? "Alert" : "Banner";
+}
+
+}  // namespace
+
+void LogMacNotificationActionReceived(bool is_alert, bool is_valid) {
+  base::UmaHistogramBoolean(base::StrCat({"Notifications.macOS.ActionReceived.",
+                                          NotificationStyleSuffix(is_alert)}),
+                            is_valid);
+}
+
+void LogMacNotificationDelivered(bool is_alert, bool success) {
+  base::UmaHistogramBoolean(base::StrCat({"Notifications.macOS.Delivered.",
+                                          NotificationStyleSuffix(is_alert)}),
+                            success);
+}
diff --git a/chrome/browser/notifications/notification_platform_bridge_mac_metrics.h b/chrome/browser/notifications/notification_platform_bridge_mac_metrics.h
new file mode 100644
index 0000000..5b6a3e1
--- /dev/null
+++ b/chrome/browser/notifications/notification_platform_bridge_mac_metrics.h
@@ -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.
+
+#ifndef CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_PLATFORM_BRIDGE_MAC_METRICS_H_
+#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_PLATFORM_BRIDGE_MAC_METRICS_H_
+
+// Called when a user performed an action on a notification on macOS.
+// |is_alert| determines if the notification was an alert or a banner.
+// |is_valid| determines if the action data was valid and we passed it along.
+void LogMacNotificationActionReceived(bool is_alert, bool is_valid);
+
+// Called when we delivered a new notification to the macOS notification center.
+// |is_alert| determines if the notification was an alert or a banner.
+// |success| determines if there was an error while delivering the notification.
+void LogMacNotificationDelivered(bool is_alert, bool success);
+
+#endif  // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_PLATFORM_BRIDGE_MAC_METRICS_H_
diff --git a/chrome/browser/notifications/notification_platform_bridge_mac_unittest.mm b/chrome/browser/notifications/notification_platform_bridge_mac_unittest.mm
index 2f61aff..1c4c5577 100644
--- a/chrome/browser/notifications/notification_platform_bridge_mac_unittest.mm
+++ b/chrome/browser/notifications/notification_platform_bridge_mac_unittest.mm
@@ -14,6 +14,7 @@
 #include "base/run_loop.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/notifications/notification_platform_bridge_mac.h"
 #include "chrome/browser/notifications/notification_platform_bridge_mac_utils.h"
 #include "chrome/browser/notifications/notification_test_util.h"
@@ -133,6 +134,7 @@
 };
 
 TEST_F(NotificationPlatformBridgeMacTest, TestDisplayNoButtons) {
+  base::HistogramTester histogram_tester;
   std::unique_ptr<Notification> notification =
       CreateBanner("Title", "Context", "https://gmail.com", nullptr, nullptr);
 
@@ -153,6 +155,9 @@
 
   if (!base::mac::IsAtLeastOS11())
     EXPECT_NSEQ(@"Close", [delivered_notification otherButtonTitle]);
+
+  histogram_tester.ExpectUniqueSample("Notifications.macOS.Delivered.Banner",
+                                      /*sample=*/true, /*expected_count=*/1);
 }
 
 TEST_F(NotificationPlatformBridgeMacTest, TestIncognitoProfile) {
@@ -363,6 +368,7 @@
   if (!MacOSSupportsXPCAlerts())
     return;
 
+  base::HistogramTester histogram_tester;
   std::unique_ptr<Notification> alert =
       CreateAlert("Title", "Context", "https://gmail.com", "Button 1", nullptr);
   std::unique_ptr<NotificationPlatformBridgeMac> bridge(
@@ -372,6 +378,8 @@
                   nullptr);
   EXPECT_EQ(0u, [[notification_center() deliveredNotifications] count]);
   EXPECT_EQ(1u, [[alert_dispatcher() alerts] count]);
+  histogram_tester.ExpectUniqueSample("Notifications.macOS.Delivered.Alert",
+                                      /*sample=*/true, /*expected_count=*/1);
 }
 
 TEST_F(NotificationPlatformBridgeMacTest, TestDisplayBannerAndAlert) {
@@ -493,6 +501,7 @@
 }
 
 TEST_F(NotificationPlatformBridgeMacTest, DidActivateNotification) {
+  base::HistogramTester histogram_tester;
   auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
       notification_center(), alert_dispatcher());
 
@@ -513,4 +522,8 @@
 
   // Handling responses is async, make sure we wait for all tasks to complete.
   task_environment_.RunUntilIdle();
+
+  histogram_tester.ExpectUniqueSample(
+      "Notifications.macOS.ActionReceived.Banner", /*sample=*/true,
+      /*expected_count=*/1);
 }
diff --git a/chrome/browser/notifications/notification_platform_bridge_mac_unnotification.mm b/chrome/browser/notifications/notification_platform_bridge_mac_unnotification.mm
index 82d1116..5a3e0f0 100644
--- a/chrome/browser/notifications/notification_platform_bridge_mac_unnotification.mm
+++ b/chrome/browser/notifications/notification_platform_bridge_mac_unnotification.mm
@@ -16,6 +16,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/thread_pool.h"
 #include "base/timer/timer.h"
+#include "chrome/browser/notifications/notification_platform_bridge_mac_metrics.h"
 #include "chrome/browser/notifications/notification_platform_bridge_mac_utils.h"
 #include "chrome/browser/notifications/unnotification_metrics.h"
 #include "chrome/browser/profiles/profile.h"
@@ -161,6 +162,7 @@
   [builder setIdentifier:notification_id];
 
   if (is_alert) {
+    LogMacNotificationDelivered(is_alert, /*success=*/true);
     NSDictionary* dict = [builder buildDictionary];
     [alert_dispatcher_ dispatchNotification:dict];
     [builder setClosedFromAlert:YES];
@@ -184,6 +186,7 @@
 
   void (^notification_delivered_block)(NSError* _Nullable) = ^(
       NSError* _Nullable error) {
+    LogMacNotificationDelivered(is_alert, /*success=*/!error);
     if (error != nil) {
       DVLOG(1) << "Notification request did not succeed";
       return;
diff --git a/chrome/browser/notifications/notification_platform_bridge_mac_unnotification_unittest.mm b/chrome/browser/notifications/notification_platform_bridge_mac_unnotification_unittest.mm
index 7ff8879..0679a6f8 100644
--- a/chrome/browser/notifications/notification_platform_bridge_mac_unnotification_unittest.mm
+++ b/chrome/browser/notifications/notification_platform_bridge_mac_unnotification_unittest.mm
@@ -113,6 +113,7 @@
 
 TEST_F(UNNotificationPlatformBridgeMacTest, TestDisplay) {
   if (@available(macOS 10.14, *)) {
+    base::HistogramTester histogram_tester;
     Notification notification = CreateNotification();
 
     bridge_->Display(NotificationHandler::Type::WEB_PERSISTENT, profile_,
@@ -133,11 +134,15 @@
                  NSSet<UNNotificationCategory*>* categories) {
       EXPECT_EQ(1u, [categories count]);
     }];
+
+    histogram_tester.ExpectUniqueSample("Notifications.macOS.Delivered.Banner",
+                                        /*sample=*/true, /*expected_count=*/1);
   }
 }
 
 TEST_F(UNNotificationPlatformBridgeMacTest, TestDisplayAlert) {
   if (@available(macOS 10.14, *)) {
+    base::HistogramTester histogram_tester;
     Notification alert = CreateAlert();
     // Some OS versions don't support alerts.
     if (!IsAlertNotificationMac(alert))
@@ -168,6 +173,9 @@
     EXPECT_NSEQ(@"Context", informative_text);
     EXPECT_NSEQ(@"gmail.com", subtitle);
     EXPECT_NSEQ(@"r|Moe|id1", identifier);
+
+    histogram_tester.ExpectUniqueSample("Notifications.macOS.Delivered.Alert",
+                                        /*sample=*/true, /*expected_count=*/1);
   }
 }
 
@@ -588,6 +596,7 @@
 
 TEST_F(UNNotificationPlatformBridgeMacTest, TestSynchronizeNotifications) {
   if (@available(macOS 10.14, *)) {
+    base::HistogramTester histogram_tester;
     Notification banner1 = CreateNotification("banner1");
     Notification banner2 = CreateNotification("banner2");
     Notification alert1 = CreateAlert("alert1");
@@ -626,6 +635,13 @@
     display_service_tester_->SetProcessNotificationOperationDelegate(
         operation_callback.Get());
     task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(5));
+
+    histogram_tester.ExpectUniqueSample(
+        "Notifications.macOS.ActionReceived.Alert", /*sample=*/true,
+        /*expected_count=*/1);
+    histogram_tester.ExpectUniqueSample(
+        "Notifications.macOS.ActionReceived.Banner", /*sample=*/true,
+        /*expected_count=*/1);
   }
 }
 
@@ -712,6 +728,8 @@
 
 TEST_F(UNNotificationPlatformBridgeMacTest, NotificationResponse) {
   if (@available(macOS 10.14, *)) {
+    base::HistogramTester histogram_tester;
+
     base::scoped_nsobject<FakeUNNotificationResponse> fakeResponse =
         CreateFakeUNNotificationResponse(@{
           notification_constants::kNotificationOrigin : @"https://google.com",
@@ -733,5 +751,9 @@
 
     // Handling responses is async, make sure we wait for all tasks to complete.
     task_environment_.RunUntilIdle();
+
+    histogram_tester.ExpectUniqueSample(
+        "Notifications.macOS.ActionReceived.Banner", /*sample=*/true,
+        /*expected_count=*/1);
   }
 }
diff --git a/chrome/browser/notifications/notification_platform_bridge_mac_utils.mm b/chrome/browser/notifications/notification_platform_bridge_mac_utils.mm
index 6bb2731..9b6503f 100644
--- a/chrome/browser/notifications/notification_platform_bridge_mac_utils.mm
+++ b/chrome/browser/notifications/notification_platform_bridge_mac_utils.mm
@@ -12,6 +12,7 @@
 #include "base/system/sys_info.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/notifications/notification_display_service_impl.h"
+#include "chrome/browser/notifications/notification_platform_bridge_mac_metrics.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/services/mac_notifications/public/cpp/notification_constants_mac.h"
@@ -201,7 +202,12 @@
 }
 
 void ProcessMacNotificationResponse(NSDictionary* response) {
-  if (!VerifyMacNotificationData(response))
+  bool isAlert = [[response
+      objectForKey:notification_constants::kNotificationIsAlert] boolValue];
+  bool isValid = VerifyMacNotificationData(response);
+  LogMacNotificationActionReceived(isAlert, isValid);
+
+  if (!isValid)
     return;
 
   NSNumber* buttonIndex =
diff --git a/chrome/browser/optimization_guide/page_text_observer_browsertest.cc b/chrome/browser/optimization_guide/page_text_observer_browsertest.cc
index 729208c..ed4dfc5d 100644
--- a/chrome/browser/optimization_guide/page_text_observer_browsertest.cc
+++ b/chrome/browser/optimization_guide/page_text_observer_browsertest.cc
@@ -95,9 +95,8 @@
     host_resolver()->AddRule("*", "127.0.0.1");
     InProcessBrowserTest::SetUpOnMainThread();
 
-    embedded_test_server()->RegisterRequestHandler(
-        base::BindRepeating(&PageTextObserverBrowserTest::HandleSlowRequest,
-                            base::Unretained(this)));
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &PageTextObserverBrowserTest::RequestHandler, base::Unretained(this)));
     embedded_test_server()->ServeFilesFromSourceDirectory(
         "chrome/test/data/optimization_guide");
 
@@ -112,13 +111,17 @@
     return PageTextObserver::FromWebContents(web_contents());
   }
 
+ protected:
+  std::string dynamic_response_body_;
+
  private:
-  // This script is render blocking in the HTML, but is intentionally slow. This
-  // provides important time between commit and first layout for any text dump
-  // requests to make it to the renderer, reducing flakes.
-  std::unique_ptr<net::test_server::HttpResponse> HandleSlowRequest(
+  std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
       const net::test_server::HttpRequest& request) {
     std::string path_value;
+
+    // This script is render blocking in the HTML, but is intentionally slow.
+    // This provides important time between commit and first layout for any text
+    // dump requests to make it to the renderer, reducing flakes.
     if (request.GetURL().path() == "/slow-first-layout.js") {
       std::unique_ptr<net::test_server::DelayedHttpResponse> resp =
           std::make_unique<net::test_server::DelayedHttpResponse>(
@@ -128,6 +131,16 @@
       resp->set_content(std::string());
       return resp;
     }
+
+    if (request.GetURL().path() == "/dynamic.html") {
+      std::unique_ptr<net::test_server::BasicHttpResponse> resp =
+          std::make_unique<net::test_server::BasicHttpResponse>();
+      resp->set_code(net::HTTP_OK);
+      resp->set_content_type("text/html");
+      resp->set_content(dynamic_response_body_);
+      return resp;
+    }
+
     return nullptr;
   }
 };
@@ -185,4 +198,42 @@
   EXPECT_EQ(base::ASCIIToUTF16("hello\n\nworld"), *on_load_consumer.text());
 }
 
+class PageTextObserverSingleProcessBrowserTest
+    : public PageTextObserverBrowserTest {
+ public:
+  PageTextObserverSingleProcessBrowserTest() = default;
+  ~PageTextObserverSingleProcessBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* cmd_line) override {
+    PageTextObserverBrowserTest::SetUpCommandLine(cmd_line);
+    cmd_line->AppendSwitch("single-process");
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(PageTextObserverSingleProcessBrowserTest,
+                       SameProcessIframe) {
+  PageTextObserver::CreateForWebContents(web_contents());
+  ASSERT_TRUE(observer());
+
+  TestConsumer consumer;
+  observer()->AddConsumer(&consumer);
+  consumer.PopulateRequest(/*max_size=*/1024,
+                           /*events=*/{mojom::TextDumpEvent::kFinishedLoad});
+
+  GURL url(embedded_test_server()->GetURL("a.com", "/dynamic.html"));
+  dynamic_response_body_ = base::StringPrintf(
+      "<html><body>"
+      "<p>foo</p>"
+      "<iframe src=\"%s\"></iframe>"
+      "</body></html>",
+      embedded_test_server()->GetURL("a.com", "/hello.html").spec().c_str());
+
+  ui_test_utils::NavigateToURL(browser(), url);
+  ASSERT_TRUE(consumer.was_called());
+
+  consumer.WaitForPageText();
+  ASSERT_TRUE(consumer.text());
+  EXPECT_EQ(base::ASCIIToUTF16("foo\n\nhello"), *consumer.text());
+}
+
 }  // namespace optimization_guide
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
index 3ad7a12..28e1d6b6 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
@@ -14,8 +14,6 @@
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h"
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h"
 #include "chrome/browser/ui/browser.h"
@@ -24,6 +22,8 @@
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/heavy_ad_intervention/heavy_ad_features.h"
 #include "components/page_load_metrics/browser/ads_page_load_metrics_test_waiter.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/frame_data.h"
 #include "components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/page_load_metrics_memory_tracker.h"
 #include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
diff --git a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
index a96e2d1f..ece1fd8 100644
--- a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
@@ -977,6 +977,10 @@
 }
 
 void UkmPageLoadMetricsObserver::ReportLayoutStability() {
+  // Don't report CLS if we were never in the foreground.
+  if (last_time_shown_.is_null())
+    return;
+
   ukm::builders::PageLoad builder(GetDelegate().GetPageUkmSourceId());
   builder
       .SetLayoutInstability_CumulativeShiftScore(
@@ -1215,25 +1219,23 @@
   else if (mf.allow_user_zoom == blink::mojom::ViewportStatus::kNo)
     builder.SetAllowUserZoom(false);
 
-  if (mf.small_text_ratio != -1) {
+  if (mf.small_text_ratio != -1)
     builder.SetSmallTextRatio(mf.small_text_ratio);
-  }
 
   if (mf.viewport_initial_scale_x10 != -1) {
     builder.SetViewportInitialScaleX10(
         ukm::GetExponentialBucketMin(mf.viewport_initial_scale_x10, 1.2));
   }
-
-  const int hardcoded_width = mf.viewport_hardcoded_width;
-  if (hardcoded_width > 0) {
+  if (mf.viewport_hardcoded_width != -1) {
     builder.SetViewportHardcodedWidth(
-        BucketWithOffsetAndUnit(hardcoded_width, 500, 10));
+        BucketWithOffsetAndUnit(mf.viewport_hardcoded_width, 500, 10));
   }
-
   if (mf.text_content_outside_viewport_percentage != -1) {
     builder.SetTextContentOutsideViewportPercentage(
         mf.text_content_outside_viewport_percentage);
   }
+  if (mf.bad_tap_targets_ratio != -1)
+    builder.SetBadTapTargetsRatio(mf.bad_tap_targets_ratio);
 
   builder.Record(ukm::UkmRecorder::Get());
 }
diff --git a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc
index 407d43f..b469b8f 100644
--- a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc
@@ -2109,6 +2109,30 @@
   }
 }
 
+TEST_F(UkmPageLoadMetricsObserverTest, CLSNeverForegroundedNoReport) {
+  web_contents()->WasHidden();
+  NavigateAndCommit(GURL(kTestUrl1));
+
+  page_load_metrics::mojom::FrameRenderDataUpdate render_data(1.0, 1.0, 0, 0, 0,
+                                                              0, {});
+  tester()->SimulateRenderDataUpdate(render_data);
+
+  // Simulate closing the tab.
+  DeleteContents();
+
+  const auto& ukm_recorder = tester()->test_ukm_recorder();
+  std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
+      ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName);
+  EXPECT_EQ(1ul, merged_entries.size());
+
+  for (const auto& kv : merged_entries) {
+    const ukm::mojom::UkmEntry* ukm_entry = kv.second.get();
+    ukm_recorder.ExpectEntrySourceHasUrl(ukm_entry, GURL(kTestUrl1));
+    EXPECT_FALSE(ukm_recorder.EntryHasMetric(
+        ukm_entry, PageLoad::kLayoutInstability_CumulativeShiftScoreName));
+  }
+}
+
 class CLSUkmPageLoadMetricsObserverTest
     : public UkmPageLoadMetricsObserverTest {
  protected:
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc b/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc
index f22430d..9ee34956 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/heavy_ad_intervention/heavy_ad_service_factory.h"
 #include "chrome/browser/page_load_metrics/observers/aborts_page_load_metrics_observer.h"
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/observers/ad_metrics/floc_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.h"
@@ -48,6 +47,7 @@
 #include "chrome/browser/search/search.h"
 #include "components/no_state_prefetch/browser/no_state_prefetch_contents.h"
 #include "components/page_load_metrics/browser/metrics_web_contents_observer.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/page_load_metrics_embedder_base.h"
 #include "components/page_load_metrics/browser/page_load_metrics_memory_tracker.h"
 #include "components/page_load_metrics/browser/page_load_tracker.h"
diff --git a/chrome/browser/paint_preview/android/BUILD.gn b/chrome/browser/paint_preview/android/BUILD.gn
index 3e6af7f..584fe22 100644
--- a/chrome/browser/paint_preview/android/BUILD.gn
+++ b/chrome/browser/paint_preview/android/BUILD.gn
@@ -72,6 +72,7 @@
     "//chrome/browser/flags:java",
     "//chrome/browser/tab:java",
     "//chrome/browser/tabmodel:java",
+    "//chrome/browser/ui/android/appmenu/test:test_support_java",
     "//chrome/browser/ui/messages/android:java",
     "//chrome/test/android:chrome_java_test_support",
     "//components/paint_preview/browser/android:java",
diff --git a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/DemoPaintPreviewTest.java b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/DemoPaintPreviewTest.java
index 0dfdd98..9b682811 100644
--- a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/DemoPaintPreviewTest.java
+++ b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/DemoPaintPreviewTest.java
@@ -7,14 +7,12 @@
 import static org.chromium.base.test.util.Batch.PER_CLASS;
 import static org.chromium.chrome.browser.paint_preview.TabbedPaintPreviewTest.assertAttachedAndShown;
 
-import android.support.test.InstrumentationRegistry;
-import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiSelector;
 
 import androidx.test.filters.MediumTest;
 
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
@@ -26,9 +24,12 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.test.util.Batch;
+import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.paint_preview.services.PaintPreviewTabService;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuTestSupport;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
@@ -95,9 +96,13 @@
                 .when(sMockService)
                 .captureTab(Mockito.any(Tab.class), mCallbackCaptor.capture());
 
-        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        uiDevice.pressMenu();
-        uiDevice.findObject(new UiSelector().text("Show Paint Preview")).click();
+        AppMenuCoordinator coordinator = sActivityTestRule.getAppMenuCoordinator();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { AppMenuTestSupport.showAppMenu(coordinator, null, false); });
+        Assert.assertNotNull(
+                AppMenuTestSupport.getMenu(coordinator).findItem(R.id.paint_preview_show_id));
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> AppMenuTestSupport.callOnItemClick(coordinator, R.id.paint_preview_show_id));
 
         Tab tab = sActivityTestRule.getActivity().getActivityTab();
         TabbedPaintPreview tabbedPaintPreview =
diff --git a/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc b/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc
index 45fcb0b..966bbcf 100644
--- a/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc
@@ -18,6 +18,7 @@
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/test/browser_task_environment.h"
@@ -38,42 +39,6 @@
 namespace reporting {
 namespace {
 
-// Usage (in tests only):
-//
-//   TestEvent<ResType> e;
-//   ... Do some async work passing e.cb() as a completion callback of
-//   base::OnceCallback<void(ResType* res)> type which also may perform some
-//   other action specified by |done| callback provided by the caller.
-//   ... = e.result();  // Will wait for e.cb() to be called and return the
-//   collected result.
-//
-template <typename ResType>
-class TestEvent {
- public:
-  TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
-  ~TestEvent() = default;
-  TestEvent(const TestEvent& other) = delete;
-  TestEvent& operator=(const TestEvent& other) = delete;
-  ResType result() {
-    run_loop_->Run();
-    return std::forward<ResType>(result_);
-  }
-
-  // Completion callback to hand over to the processing method.
-  base::OnceCallback<void(ResType res)> cb() {
-    return base::BindOnce(
-        [](base::RunLoop* run_loop, ResType* result, ResType res) {
-          *result = std::forward<ResType>(res);
-          run_loop->Quit();
-        },
-        base::Unretained(run_loop_.get()), base::Unretained(&result_));
-  }
-
- private:
-  std::unique_ptr<base::RunLoop> run_loop_;
-  ResType result_;
-};
-
 class ReportClientTest : public testing::Test {
  public:
   void SetUp() override {
@@ -135,7 +100,7 @@
       dm_token_, destination_, policy_checker_callback_);
   ASSERT_OK(config_result);
 
-  TestEvent<StatusOr<std::unique_ptr<ReportQueue>>> a;
+  test::TestEvent<StatusOr<std::unique_ptr<ReportQueue>>> a;
   ReportQueueProvider::CreateQueue(std::move(config_result.ValueOrDie()),
                                    a.cb());
   ASSERT_OK(a.result());
@@ -147,14 +112,14 @@
       dm_token_, destination_, policy_checker_callback_);
   EXPECT_TRUE(config_result.ok());
 
-  TestEvent<StatusOr<std::unique_ptr<ReportQueue>>> a1;
+  test::TestEvent<StatusOr<std::unique_ptr<ReportQueue>>> a1;
   ReportQueueProvider::CreateQueue(std::move(config_result.ValueOrDie()),
                                    a1.cb());
   auto result = a1.result();
   ASSERT_OK(result);
   auto report_queue_1 = std::move(result.ValueOrDie());
 
-  TestEvent<StatusOr<std::unique_ptr<ReportQueue>>> a2;
+  test::TestEvent<StatusOr<std::unique_ptr<ReportQueue>>> a2;
   config_result = ReportQueueConfiguration::Create(dm_token_, destination_,
                                                    policy_checker_callback_);
   ReportQueueProvider::CreateQueue(std::move(config_result.ValueOrDie()),
@@ -172,14 +137,14 @@
       dm_token_, destination_, policy_checker_callback_);
   EXPECT_TRUE(config_result.ok());
 
-  TestEvent<StatusOr<std::unique_ptr<ReportQueue>>> create_queue_event;
+  test::TestEvent<StatusOr<std::unique_ptr<ReportQueue>>> create_queue_event;
   ReportQueueProvider::CreateQueue(std::move(config_result.ValueOrDie()),
                                    create_queue_event.cb());
   auto result = create_queue_event.result();
   ASSERT_OK(result);
   auto report_queue = std::move(result.ValueOrDie());
 
-  TestEvent<Status> enqueue_record_event;
+  test::TestEvent<Status> enqueue_record_event;
   report_queue->Enqueue("Record", FAST_BATCH, enqueue_record_event.cb());
   ASSERT_OK(enqueue_record_event.result());
 
diff --git a/chrome/browser/policy/messaging_layer/public/report_queue_impl_unittest.cc b/chrome/browser/policy/messaging_layer/public/report_queue_impl_unittest.cc
index 121e1a5..9492434 100644
--- a/chrome/browser/policy/messaging_layer/public/report_queue_impl_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/public/report_queue_impl_unittest.cc
@@ -22,6 +22,7 @@
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -38,42 +39,6 @@
 namespace reporting {
 namespace {
 
-// Usage (in tests only):
-//
-//   TestEvent<ResType> e;
-//   ... Do some async work passing e.cb() as a completion callback of
-//   base::OnceCallback<void(ResType* res)> type which also may perform some
-//   other action specified by |done| callback provided by the caller.
-//   ... = e.result();  // Will wait for e.cb() to be called and return the
-//   collected result.
-//
-template <typename ResType>
-class TestEvent {
- public:
-  TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
-  ~TestEvent() = default;
-  TestEvent(const TestEvent& other) = delete;
-  TestEvent& operator=(const TestEvent& other) = delete;
-  ResType result() {
-    run_loop_->Run();
-    return std::forward<ResType>(result_);
-  }
-
-  // Completion callback to hand over to the processing method.
-  base::OnceCallback<void(ResType res)> cb() {
-    return base::BindOnce(
-        [](base::RunLoop* run_loop, ResType* result, ResType res) {
-          *result = std::forward<ResType>(res);
-          run_loop->Quit();
-        },
-        base::Unretained(run_loop_.get()), base::Unretained(&result_));
-  }
-
- private:
-  std::unique_ptr<base::RunLoop> run_loop_;
-  ResType result_;
-};
-
 // Creates a |ReportQueue| using |TestStorageModule| and
 // |TestEncryptionModule|. Allows access to the storage module for checking
 // stored values.
@@ -134,7 +99,7 @@
 // |StorageModuleInterface|.
 TEST_F(ReportQueueImplTest, SuccessfulStringRecord) {
   constexpr char kTestString[] = "El-Chupacabra";
-  TestEvent<Status> a;
+  test::TestEvent<Status> a;
   report_queue_->Enqueue(kTestString, priority_, a.cb());
   EXPECT_OK(a.result());
   EXPECT_EQ(test_storage_module()->priority(), priority_);
@@ -148,7 +113,7 @@
   constexpr char kTestValue[] = "TEST_VALUE";
   base::Value test_dict(base::Value::Type::DICTIONARY);
   test_dict.SetStringKey(kTestKey, kTestValue);
-  TestEvent<Status> a;
+  test::TestEvent<Status> a;
   report_queue_->Enqueue(test_dict, priority_, a.cb());
   EXPECT_OK(a.result());
 
@@ -165,7 +130,7 @@
 TEST_F(ReportQueueImplTest, SuccessfulProtoRecord) {
   reporting::test::TestMessage test_message;
   test_message.set_test("TEST_MESSAGE");
-  TestEvent<Status> a;
+  test::TestEvent<Status> a;
   report_queue_->Enqueue(&test_message, priority_, a.cb());
   EXPECT_OK(a.result());
 
@@ -189,7 +154,7 @@
 
   reporting::test::TestMessage test_message;
   test_message.set_test("TEST_MESSAGE");
-  TestEvent<Status> a;
+  test::TestEvent<Status> a;
   report_queue_->Enqueue(&test_message, priority_, a.cb());
   const auto result = a.result();
   EXPECT_FALSE(result.ok());
@@ -200,7 +165,7 @@
   EXPECT_CALL(mocked_policy_check_, Call())
       .WillOnce(Return(Status(error::UNAUTHENTICATED, "Failing for tests")));
   constexpr char kTestString[] = "El-Chupacabra";
-  TestEvent<Status> a;
+  test::TestEvent<Status> a;
   report_queue_->Enqueue(kTestString, priority_, a.cb());
   const auto result = a.result();
   EXPECT_FALSE(result.ok());
@@ -212,7 +177,7 @@
       .WillOnce(Return(Status(error::UNAUTHENTICATED, "Failing for tests")));
   reporting::test::TestMessage test_message;
   test_message.set_test("TEST_MESSAGE");
-  TestEvent<Status> a;
+  test::TestEvent<Status> a;
   report_queue_->Enqueue(&test_message, priority_, a.cb());
   const auto result = a.result();
   EXPECT_FALSE(result.ok());
@@ -226,7 +191,7 @@
   constexpr char kTestValue[] = "TEST_VALUE";
   base::Value test_dict(base::Value::Type::DICTIONARY);
   test_dict.SetStringKey(kTestKey, kTestValue);
-  TestEvent<Status> a;
+  test::TestEvent<Status> a;
   report_queue_->Enqueue(test_dict, priority_, a.cb());
   const auto result = a.result();
   EXPECT_FALSE(result.ok());
diff --git a/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service_unittest.cc b/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service_unittest.cc
index a22334a2..0ee006e 100644
--- a/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/dm_server_upload_service_unittest.cc
@@ -18,6 +18,7 @@
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
 #include "components/reporting/util/shared_vector.h"
 #include "components/reporting/util/status.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -25,83 +26,25 @@
 namespace {
 
 using ::testing::_;
+using ::testing::Eq;
 using ::testing::Invoke;
 using ::testing::MockFunction;
+using ::testing::Property;
 using ::testing::Return;
 using ::testing::StrictMock;
 using ::testing::WithArgs;
 
-// Usage (in tests only):
-//
-//   TestEvent<ResType> e;
-//   ... Do some async work passing e.cb() as a completion callback of
-//   base::OnceCallback<void(ResType* res)> type which also may perform some
-//   other action specified by |done| callback provided by the caller.
-//   ... = e.result();  // Will wait for e.cb() to be called and return the
-//   collected result.
-//
-template <typename ResType>
-class TestEvent {
- public:
-  TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
-  ~TestEvent() = default;
-  TestEvent(const TestEvent& other) = delete;
-  TestEvent& operator=(const TestEvent& other) = delete;
-  ResType result() {
-    run_loop_->Run();
-    return std::forward<ResType>(result_);
-  }
-
-  // Completion callback to hand over to the processing method.
-  base::OnceCallback<void(ResType res)> cb() {
-    return base::BindOnce(
-        [](base::RunLoop* run_loop, ResType* result, ResType res) {
-          *result = std::forward<ResType>(res);
-          run_loop->Quit();
-        },
-        base::Unretained(run_loop_.get()), base::Unretained(&result_));
-  }
-
- private:
-  std::unique_ptr<base::RunLoop> run_loop_;
-  ResType result_;
-};
-
 // Ensures that profile cannot be null.
 TEST(DmServerUploadServiceTest, DeniesNullptrProfile) {
   content::BrowserTaskEnvironment task_envrionment;
-  TestEvent<StatusOr<std::unique_ptr<DmServerUploadService>>> e;
+  test::TestEvent<StatusOr<std::unique_ptr<DmServerUploadService>>> e;
   DmServerUploadService::Create(/*client=*/nullptr, base::DoNothing(),
                                 base::DoNothing(), e.cb());
   StatusOr<std::unique_ptr<DmServerUploadService>> result = e.result();
-  EXPECT_FALSE(result.ok());
-  EXPECT_EQ(result.status().error_code(), error::INVALID_ARGUMENT);
+  EXPECT_THAT(result.status(),
+              Property(&Status::error_code, Eq(error::INVALID_ARGUMENT)));
 }
 
-class TestCallbackWaiter {
- public:
-  TestCallbackWaiter() : run_loop_(std::make_unique<base::RunLoop>()) {}
-
-  DmServerUploadService::CompletionCallback cb() {
-    return base::BindOnce(
-        [](TestCallbackWaiter* self,
-           DmServerUploadService::CompletionResponse response) {
-          self->response_ = std::move(response);
-          self->run_loop_->Quit();
-        },
-        base::Unretained(this));
-  }
-
-  DmServerUploadService::CompletionResponse result() {
-    run_loop_->Run();
-    return std::move(response_);
-  }
-
- private:
-  DmServerUploadService::CompletionResponse response_;
-  std::unique_ptr<base::RunLoop> run_loop_;
-};
-
 class TestRecordHandler : public DmServerUploadService::RecordHandler {
  public:
   TestRecordHandler() : RecordHandler(/*client=*/nullptr) {}
@@ -178,7 +121,7 @@
       base::BindRepeating(&TestEncryptionKeyAttached::Call,
                           base::Unretained(&encryption_key_attached));
 
-  TestCallbackWaiter callback_waiter;
+  test::TestEvent<DmServerUploadService::CompletionResponse> callback_waiter;
   Start<DmServerUploadService::DmServerUploader>(
       need_encryption_key(), std::move(records_), handler_.get(),
       callback_waiter.cb(), encryption_key_attached_cb, sequenced_task_runner_);
@@ -226,7 +169,7 @@
       base::BindRepeating(&TestEncryptionKeyAttached::Call,
                           base::Unretained(&encryption_key_attached));
 
-  TestCallbackWaiter callback_waiter;
+  test::TestEvent<DmServerUploadService::CompletionResponse> callback_waiter;
   Start<DmServerUploadService::DmServerUploader>(
       need_encryption_key(), std::move(records_), handler_.get(),
       callback_waiter.cb(), encryption_key_attached_cb, sequenced_task_runner_);
@@ -252,14 +195,14 @@
       base::BindRepeating(&TestEncryptionKeyAttached::Call,
                           base::Unretained(&encryption_key_attached));
 
-  TestCallbackWaiter callback_waiter;
+  test::TestEvent<DmServerUploadService::CompletionResponse> callback_waiter;
   Start<DmServerUploadService::DmServerUploader>(
       need_encryption_key(), std::move(records_), handler_.get(),
       callback_waiter.cb(), encryption_key_attached_cb, sequenced_task_runner_);
 
   const auto response = callback_waiter.result();
-  EXPECT_FALSE(response.ok());
-  EXPECT_EQ(response.status().error_code(), error::FAILED_PRECONDITION);
+  EXPECT_THAT(response.status(),
+              Property(&Status::error_code, Eq(error::FAILED_PRECONDITION)));
 }
 
 TEST_P(DmServerUploaderTest, ReportsFailureToUpload) {
@@ -279,14 +222,14 @@
       base::BindRepeating(&TestEncryptionKeyAttached::Call,
                           base::Unretained(&encryption_key_attached));
 
-  TestCallbackWaiter callback_waiter;
+  test::TestEvent<DmServerUploadService::CompletionResponse> callback_waiter;
   Start<DmServerUploadService::DmServerUploader>(
       need_encryption_key(), std::move(records_), handler_.get(),
       callback_waiter.cb(), encryption_key_attached_cb, sequenced_task_runner_);
 
   const auto response = callback_waiter.result();
-  EXPECT_FALSE(response.ok());
-  EXPECT_EQ(response.status().error_code(), error::DEADLINE_EXCEEDED);
+  EXPECT_THAT(response.status(),
+              Property(&Status::error_code, Eq(error::DEADLINE_EXCEEDED)));
 }
 
 TEST_P(DmServerUploaderTest, ReprotWithZeroRecords) {
@@ -317,7 +260,7 @@
     EXPECT_CALL(*handler_, HandleRecords_(_, _, _, _)).Times(0);
   }
 
-  TestCallbackWaiter callback_waiter;
+  test::TestEvent<DmServerUploadService::CompletionResponse> callback_waiter;
   Start<DmServerUploadService::DmServerUploader>(
       need_encryption_key(), std::move(records_), handler_.get(),
       callback_waiter.cb(), encryption_key_attached_cb, sequenced_task_runner_);
@@ -326,8 +269,8 @@
   if (need_encryption_key()) {
     EXPECT_OK(response);
   } else {
-    EXPECT_FALSE(response.ok());
-    EXPECT_EQ(response.status().error_code(), error::INVALID_ARGUMENT);
+    EXPECT_THAT(response.status(),
+                Property(&Status::error_code, Eq(error::INVALID_ARGUMENT)));
   }
 }
 
diff --git a/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc b/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
index eae8702..6ea2e69 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
@@ -26,6 +26,7 @@
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
 #include "components/reporting/util/task_runner_context.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -62,43 +63,6 @@
   return arg.ValueOrDie().force_confirm == expected.force_confirm;
 }
 
-MATCHER_P(StatusOrErrorCodeEquals,
-          expected,
-          "Compares StatusOr<T>.status().error_code() to expected") {
-  return arg.status().error_code() == expected;
-}
-
-class TestCallbackWaiter {
- public:
-  TestCallbackWaiter() = default;
-
-  virtual void Signal() { run_loop_.Quit(); }
-
-  void Wait() { run_loop_.Run(); }
-
- protected:
-  base::RunLoop run_loop_;
-};
-
-class TestCallbackWaiterWithCounter : public TestCallbackWaiter {
- public:
-  explicit TestCallbackWaiterWithCounter(size_t counter_limit)
-      : counter_limit_(counter_limit) {}
-
-  void Signal() override {
-    DCHECK_GT(counter_limit_, 0u);
-    if (--counter_limit_ == 0u) {
-      run_loop_.Quit();
-    }
-  }
-
- private:
-  std::atomic<size_t> counter_limit_;
-};
-
-using TestCompletionResponder =
-    MockFunction<void(DmServerUploadService::CompletionResponse)>;
-
 using TestEncryptionKeyAttached = MockFunction<void(SignedEncryptionInfo)>;
 
 // Helper function for retrieving and processing the SequencingInformation from
@@ -266,53 +230,38 @@
   auto test_records = BuildTestRecordsVector(kNumTestRecords, kGenerationId);
   const auto force_confirm_by_server = force_confirm();
 
-  TestCallbackWaiter client_waiter;
+  DmServerUploadService::SuccessfulUploadResponse expected_response{
+      .sequencing_information = test_records->back().sequencing_information(),
+      .force_confirm = force_confirm()};
+
   EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
       .WillOnce(WithArgs<0, 2>(
-          Invoke([&client_waiter, &force_confirm_by_server](
+          Invoke([&force_confirm_by_server](
                      base::Value request,
                      policy::CloudPolicyClient::ResponseCallback callback) {
             base::Value response{base::Value::Type::DICTIONARY};
             SucceedResponseFromRequest(request, force_confirm_by_server,
                                        response);
             std::move(callback).Run(std::move(response));
-            client_waiter.Signal();
           })));
 
-  StrictMock<TestEncryptionKeyAttached> encryption_key_attached;
-  StrictMock<TestCompletionResponder> responder;
-  TestCallbackWaiter responder_waiter;
-
-  EXPECT_CALL(
-      encryption_key_attached,
-      Call(AllOf(Property(&SignedEncryptionInfo::public_asymmetric_key,
-                          Not(IsEmpty())),
-                 Property(&SignedEncryptionInfo::public_key_id, Gt(0)),
-                 Property(&SignedEncryptionInfo::signature, Not(IsEmpty())))))
-      .Times(need_encryption_key() ? 1 : 0);
-
-  EXPECT_CALL(
-      responder,
-      Call(ResponseEquals(DmServerUploadService::SuccessfulUploadResponse{
-          .sequencing_information =
-              test_records->back().sequencing_information(),
-          .force_confirm = force_confirm()})))
-      .WillOnce(Invoke([&responder_waiter]() { responder_waiter.Signal(); }));
-
-  auto encryption_key_attached_callback =
-      base::BindRepeating(&TestEncryptionKeyAttached::Call,
-                          base::Unretained(&encryption_key_attached));
-
-  auto responder_callback = base::BindOnce(&TestCompletionResponder::Call,
-                                           base::Unretained(&responder));
+  test::TestEvent<SignedEncryptionInfo> encryption_key_attached_event;
+  test::TestEvent<DmServerUploadService::CompletionResponse> responder_event;
 
   RecordHandlerImpl handler(client_.get());
   handler.HandleRecords(need_encryption_key(), std::move(test_records),
-                        std::move(responder_callback),
-                        encryption_key_attached_callback);
-
-  client_waiter.Wait();
-  responder_waiter.Wait();
+                        responder_event.cb(),
+                        encryption_key_attached_event.cb());
+  if (need_encryption_key()) {
+    EXPECT_THAT(
+        encryption_key_attached_event.result(),
+        AllOf(Property(&SignedEncryptionInfo::public_asymmetric_key,
+                       Not(IsEmpty())),
+              Property(&SignedEncryptionInfo::public_key_id, Gt(0)),
+              Property(&SignedEncryptionInfo::signature, Not(IsEmpty()))));
+  }
+  auto response = responder_event.result();
+  EXPECT_THAT(response, ResponseEquals(expected_response));
 }
 
 TEST_P(RecordHandlerImplTest, ReportsUploadFailure) {
@@ -320,19 +269,13 @@
   static constexpr int64_t kGenerationId = 1234;
   auto test_records = BuildTestRecordsVector(kNumTestRecords, kGenerationId);
 
-  TestCallbackWaiter client_waiter;
   EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
       .WillOnce(WithArgs<2>(Invoke(
-          [&client_waiter](
-              base::OnceCallback<void(base::Optional<base::Value>)> callback) {
+          [](base::OnceCallback<void(base::Optional<base::Value>)> callback) {
             std::move(callback).Run(base::nullopt);
-            client_waiter.Signal();
           })));
 
-  StrictMock<TestCompletionResponder> responder;
-  TestCallbackWaiter responder_waiter;
-  EXPECT_CALL(responder, Call(StatusOrErrorCodeEquals(error::INTERNAL)))
-      .WillOnce(Invoke([&responder_waiter]() { responder_waiter.Signal(); }));
+  test::TestEvent<DmServerUploadService::CompletionResponse> response_event;
 
   StrictMock<TestEncryptionKeyAttached> encryption_key_attached;
   EXPECT_CALL(encryption_key_attached, Call(_)).Times(0);
@@ -341,16 +284,14 @@
       base::BindRepeating(&TestEncryptionKeyAttached::Call,
                           base::Unretained(&encryption_key_attached));
 
-  auto responder_callback = base::BindOnce(&TestCompletionResponder::Call,
-                                           base::Unretained(&responder));
-
   RecordHandlerImpl handler(client_.get());
   handler.HandleRecords(need_encryption_key(), std::move(test_records),
-                        std::move(responder_callback),
-                        encryption_key_attached_callback);
+                        response_event.cb(), encryption_key_attached_callback);
 
-  client_waiter.Wait();
-  responder_waiter.Wait();
+  const auto response = response_event.result();
+  EXPECT_THAT(response,
+              Property(&DmServerUploadService::CompletionResponse::status,
+                       Property(&Status::error_code, Eq(error::INTERNAL))));
 }
 
 TEST_P(RecordHandlerImplTest, UploadsGapRecordOnServerFailure) {
@@ -359,42 +300,35 @@
   auto test_records = BuildTestRecordsVector(kNumTestRecords, kGenerationId);
   const auto force_confirm_by_server = force_confirm();
 
+  const DmServerUploadService::SuccessfulUploadResponse expected_response{
+      .sequencing_information =
+          (*test_records)[kNumTestRecords - 1].sequencing_information(),
+      .force_confirm = force_confirm()};
+
   // Once for failure, and once for gap.
-  TestCallbackWaiterWithCounter client_waiter{2};
   {
     ::testing::InSequence seq;
     EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
         .WillOnce(WithArgs<0, 2>(
-            Invoke([&client_waiter](
-                       base::Value request,
-                       policy::CloudPolicyClient::ResponseCallback callback) {
+            Invoke([](base::Value request,
+                      policy::CloudPolicyClient::ResponseCallback callback) {
               base::Value response{base::Value::Type::DICTIONARY};
               FailedResponseFromRequest(request, response);
               std::move(callback).Run(std::move(response));
-              client_waiter.Signal();
             })));
     EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
         .WillOnce(WithArgs<0, 2>(
-            Invoke([&client_waiter, &force_confirm_by_server](
+            Invoke([&force_confirm_by_server](
                        base::Value request,
                        policy::CloudPolicyClient::ResponseCallback callback) {
               base::Value response{base::Value::Type::DICTIONARY};
               SucceedResponseFromRequest(request, force_confirm_by_server,
                                          response);
               std::move(callback).Run(std::move(response));
-              client_waiter.Signal();
             })));
   }
 
-  StrictMock<TestCallbackWaiter> responder_waiter;
-  TestCompletionResponder responder;
-  EXPECT_CALL(
-      responder,
-      Call(ResponseEquals(DmServerUploadService::SuccessfulUploadResponse{
-          .sequencing_information =
-              (*test_records)[kNumTestRecords - 1].sequencing_information(),
-          .force_confirm = force_confirm()})))
-      .WillOnce(Invoke([&responder_waiter]() { responder_waiter.Signal(); }));
+  test::TestEvent<DmServerUploadService::CompletionResponse> response_event;
 
   StrictMock<TestEncryptionKeyAttached> encryption_key_attached;
   EXPECT_CALL(
@@ -407,16 +341,13 @@
   auto encryption_key_attached_callback =
       base::BindRepeating(&TestEncryptionKeyAttached::Call,
                           base::Unretained(&encryption_key_attached));
-  auto responder_callback = base::BindOnce(&TestCompletionResponder::Call,
-                                           base::Unretained(&responder));
 
   RecordHandlerImpl handler(client_.get());
   handler.HandleRecords(need_encryption_key(), std::move(test_records),
-                        std::move(responder_callback),
-                        encryption_key_attached_callback);
+                        response_event.cb(), encryption_key_attached_callback);
 
-  client_waiter.Wait();
-  responder_waiter.Wait();
+  const auto response = response_event.result();
+  EXPECT_THAT(response, ResponseEquals(expected_response));
 }
 
 // There may be cases where the server and the client do not align in the
@@ -427,41 +358,29 @@
   static constexpr int64_t kGenerationId = 1234;
   auto test_records = BuildTestRecordsVector(kNumTestRecords, kGenerationId);
 
-  TestCallbackWaiter client_waiter;
   EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
       .WillOnce(WithArgs<2>(
-          Invoke([&client_waiter](
-                     policy::CloudPolicyClient::ResponseCallback callback) {
+          Invoke([](policy::CloudPolicyClient::ResponseCallback callback) {
             std::move(callback).Run(base::Value{base::Value::Type::DICTIONARY});
-            client_waiter.Signal();
           })));
 
   StrictMock<TestEncryptionKeyAttached> encryption_key_attached;
-  StrictMock<TestCompletionResponder> responder;
-  TestCallbackWaiter responder_waiter;
+  test::TestEvent<DmServerUploadService::CompletionResponse> response_event;
 
   EXPECT_CALL(encryption_key_attached, Call(_)).Times(0);
 
-  EXPECT_CALL(
-      responder,
-      Call(Property(&DmServerUploadService::CompletionResponse::status,
-                    Property(&Status::error_code, Eq(error::INTERNAL)))))
-      .WillOnce(Invoke([&responder_waiter]() { responder_waiter.Signal(); }));
-
   auto encryption_key_attached_callback =
       base::BindRepeating(&TestEncryptionKeyAttached::Call,
                           base::Unretained(&encryption_key_attached));
 
-  auto responder_callback = base::BindOnce(&TestCompletionResponder::Call,
-                                           base::Unretained(&responder));
-
   RecordHandlerImpl handler(client_.get());
   handler.HandleRecords(need_encryption_key(), std::move(test_records),
-                        std::move(responder_callback),
-                        encryption_key_attached_callback);
+                        response_event.cb(), encryption_key_attached_callback);
 
-  client_waiter.Wait();
-  responder_waiter.Wait();
+  const auto response = response_event.result();
+  EXPECT_THAT(response,
+              Property(&DmServerUploadService::CompletionResponse::status,
+                       Property(&Status::error_code, Eq(error::INTERNAL))));
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/chrome/browser/policy/messaging_layer/upload/upload_client_unittest.cc b/chrome/browser/policy/messaging_layer/upload/upload_client_unittest.cc
index b41d414..5c8f992 100644
--- a/chrome/browser/policy/messaging_layer/upload/upload_client_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/upload_client_unittest.cc
@@ -21,6 +21,7 @@
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
 #include "components/reporting/proto/record.pb.h"
 #include "components/reporting/proto/record_constants.pb.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/test/browser_task_environment.h"
@@ -58,83 +59,6 @@
   return expected_serialized == actual_serialized;
 }
 
-// Usage (in tests only):
-//
-//   TestEvent<ResType> e;
-//   ... Do some async work passing e.cb() as a completion callback of
-//   base::OnceCallback<void(ResType* res)> type which also may perform some
-//   other action specified by |done| callback provided by the caller.
-//   ... = e.result();  // Will wait for e.cb() to be called and return the
-//   collected result.
-//
-template <typename ResType>
-class TestEvent {
- public:
-  TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
-  ~TestEvent() = default;
-  TestEvent(const TestEvent& other) = delete;
-  TestEvent& operator=(const TestEvent& other) = delete;
-  ResType result() {
-    run_loop_->Run();
-    return std::forward<ResType>(result_);
-  }
-
-  // Completion callback to hand over to the processing method.
-  base::OnceCallback<void(ResType res)> cb() {
-    return base::BindOnce(
-        [](base::RunLoop* run_loop, ResType* result, ResType res) {
-          *result = std::forward<ResType>(res);
-          run_loop->Quit();
-        },
-        base::Unretained(run_loop_.get()), base::Unretained(&result_));
-  }
-
- private:
-  std::unique_ptr<base::RunLoop> run_loop_;
-  ResType result_;
-};
-
-class TestCallbackWaiter {
- public:
-  TestCallbackWaiter() : run_loop_(std::make_unique<base::RunLoop>()) {}
-
-  virtual void Signal() { run_loop_->Quit(); }
-
-  void CompleteExpectSequencingInformation(SequencingInformation expected,
-                                           bool expected_force_confirm,
-                                           SequencingInformation info,
-                                           bool force_confirm) {
-    EXPECT_THAT(info, EqualsProto(expected));
-    EXPECT_THAT(force_confirm, Eq(expected_force_confirm));
-    Signal();
-  }
-
-  void Wait() { run_loop_->Run(); }
-
- protected:
-  std::unique_ptr<base::RunLoop> run_loop_;
-};
-
-class TestCallbackWaiterWithCounter : public TestCallbackWaiter {
- public:
-  explicit TestCallbackWaiterWithCounter(size_t counter_limit)
-      : counter_limit_(counter_limit) {
-    DCHECK_GT(counter_limit, 0u);
-  }
-
-  void Signal() override {
-    const size_t old_count = counter_limit_.fetch_sub(1);
-    DCHECK_GT(old_count, 0u);
-    if (old_count > 1) {
-      return;
-    }
-    run_loop_->Quit();
-  }
-
- private:
-  std::atomic<size_t> counter_limit_;
-};
-
 // Helper function composes JSON represented as base::Value from Sequencing
 // information in request.
 base::Value ValueFromSucceededSequencingInfo(
@@ -279,30 +203,26 @@
   client->SetDMToken(
       policy::DMToken::CreateValidTokenForTesting("FAKE_DM_TOKEN").value());
 
-  TestCallbackWaiter waiter;
   const bool force_confirm_flag = force_confirm();
   EXPECT_CALL(*client, UploadEncryptedReport(_, _, _))
       .WillOnce(WithArgs<0, 2>(
-          Invoke([&waiter, &force_confirm_flag](
+          Invoke([&force_confirm_flag](
                      base::Value request,
                      policy::CloudPolicyClient::ResponseCallback response_cb) {
             std::move(response_cb)
                 .Run(ValueFromSucceededSequencingInfo(std::move(request),
                                                       force_confirm_flag));
-            base::ThreadPool::PostTask(
-                FROM_HERE, {base::TaskPriority::BEST_EFFORT},
-                base::BindOnce(&TestCallbackWaiter::Signal,
-                               base::Unretained(&waiter)));
           })));
 
-  TestCallbackWaiter completion_callback_waiter;
+  test::TestMultiEvent<SequencingInformation, bool> upload_completion;
   UploadClient::ReportSuccessfulUploadCallback completion_cb =
-      base::BindRepeating(
-          &TestCallbackWaiter::CompleteExpectSequencingInformation,
-          base::Unretained(&completion_callback_waiter),
-          records->back().sequencing_information(), force_confirm());
+      upload_completion.cb();
 
-  TestEvent<StatusOr<std::unique_ptr<UploadClient>>> e;
+  // Save last record seq info for verification.
+  const SequencingInformation last_record_seq_info =
+      records->back().sequencing_information();
+
+  test::TestEvent<StatusOr<std::unique_ptr<UploadClient>>> e;
   UploadClient::Create(client.get(), completion_cb, encryption_key_attached_cb,
                        e.cb());
   StatusOr<std::unique_ptr<UploadClient>> upload_client_result = e.result();
@@ -313,8 +233,10 @@
       upload_client->EnqueueUpload(need_encryption_key(), std::move(records));
   EXPECT_TRUE(enqueue_result.ok());
 
-  waiter.Wait();
-  completion_callback_waiter.Wait();
+  auto completion_result = upload_completion.result();
+  EXPECT_THAT(std::get<0>(completion_result),
+              EqualsProto(last_record_seq_info));
+  EXPECT_THAT(std::get<1>(completion_result), Eq(force_confirm()));
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/chrome/browser/policy/messaging_layer/util/report_queue_manual_test_context_unittest.cc b/chrome/browser/policy/messaging_layer/util/report_queue_manual_test_context_unittest.cc
index 6e0daf9..1075167f 100644
--- a/chrome/browser/policy/messaging_layer/util/report_queue_manual_test_context_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/util/report_queue_manual_test_context_unittest.cc
@@ -12,6 +12,7 @@
 #include "components/reporting/client/report_queue.h"
 #include "components/reporting/proto/record_constants.pb.h"
 #include "components/reporting/util/status.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -23,34 +24,6 @@
 using testing::Invoke;
 using testing::WithArgs;
 
-class TestCallbackWaiter {
- public:
-  TestCallbackWaiter() = default;
-
-  virtual void Signal() { run_loop_.Quit(); }
-
-  void Wait() { run_loop_.Run(); }
-
- protected:
-  base::RunLoop run_loop_;
-};
-
-class TestCallbackWaiterWithCounter : public TestCallbackWaiter {
- public:
-  explicit TestCallbackWaiterWithCounter(size_t counter_limit)
-      : counter_limit_(counter_limit) {}
-
-  void Signal() override {
-    DCHECK_GT(counter_limit_, 0u);
-    if (--counter_limit_ == 0u) {
-      run_loop_.Quit();
-    }
-  }
-
- private:
-  std::atomic<size_t> counter_limit_;
-};
-
 class ReportQueueManualTestContextTest : public testing::Test {
  protected:
   ReportQueueManualTestContextTest() = default;
@@ -88,29 +61,21 @@
   uint64_t kNumberOfMessagesToEnqueue = 5;
   base::TimeDelta kFrequency = base::TimeDelta::FromSeconds(1);
 
-  TestCallbackWaiterWithCounter enqueue_waiter{kNumberOfMessagesToEnqueue};
   EXPECT_CALL(*mock_report_queue_, AddRecord(_, _, _))
-      .WillRepeatedly(WithArgs<2>(Invoke(
-          [&enqueue_waiter](ReportQueue::EnqueueCallback enqueue_callback) {
+      .Times(kNumberOfMessagesToEnqueue)
+      .WillRepeatedly(
+          WithArgs<2>(Invoke([](ReportQueue::EnqueueCallback enqueue_callback) {
             std::move(enqueue_callback).Run(Status::StatusOK());
-            enqueue_waiter.Signal();
           })));
 
-  TestCallbackWaiter completion_waiter;
-  auto completion_cb = base::BindOnce(
-      [](TestCallbackWaiter* completion_waiter, Status status) {
-        EXPECT_TRUE(status.ok());
-        completion_waiter->Signal();
-      },
-      &completion_waiter);
-
+  test::TestEvent<Status> completion_event;
   Start<ReportQueueManualTestContext>(
       kFrequency, kNumberOfMessagesToEnqueue, destination_, priority_,
-      std::move(completion_cb),
+      completion_event.cb(),
       base::ThreadPool::CreateSequencedTaskRunner(base::TaskTraits()));
 
-  enqueue_waiter.Wait();
-  completion_waiter.Wait();
+  const Status status = completion_event.result();
+  EXPECT_OK(status) << status;
 }
 
 }  // namespace
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
index 5b4bfbc..0f984de9 100644
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -584,11 +584,6 @@
     public static final String PREFETCH_OFFLINE_COUNTER = "prefetch_notification_offline_counter";
 
     /**
-     * Whether users turn on the feature getting price drop alerts.
-     */
-    public static final String PRICE_TRACKING_PRICE_DROP_ALERTS =
-            "Chrome.PriceTracking.PriceDropAlerts";
-    /**
      * Whether users disable the PriceWelcomeMessageCard.
      */
     public static final String PRICE_TRACKING_PRICE_WELCOME_MESSAGE_CARD =
@@ -902,7 +897,6 @@
                 OFFLINE_MEASUREMENTS_LAST_CHECK_MILLIS,
                 OFFLINE_MEASUREMENTS_TIME_BETWEEN_CHECKS_MILLIS_LIST,
                 PERSISTENT_OFFLINE_CONTENT_AVAILABILITY_STATUS,
-                PRICE_TRACKING_PRICE_DROP_ALERTS,
                 PRICE_TRACKING_PRICE_WELCOME_MESSAGE_CARD,
                 PRICE_TRACKING_PRICE_WELCOME_MESSAGE_CARD_SHOW_COUNT,
                 PRICE_TRACKING_TRACK_PRICES_ON_TABS,
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/DeprecatedChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/DeprecatedChromePreferenceKeys.java
index 02790a90..d383c091 100644
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/DeprecatedChromePreferenceKeys.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/DeprecatedChromePreferenceKeys.java
@@ -20,6 +20,7 @@
         // clang-format off
         return Arrays.asList(
                 "Chrome.Flags.PaintPreviewTestEnabled",
+                "Chrome.PriceTracking.PriceDropAlerts",
                 "PersistedNotificationId",
                 "PhysicalWeb.ActivityReferral",
                 "PhysicalWeb.HasDeferredMetrics",
diff --git a/chrome/browser/printing/pdf_nup_converter_client.h b/chrome/browser/printing/pdf_nup_converter_client.h
index 794a215..be81660 100644
--- a/chrome/browser/printing/pdf_nup_converter_client.h
+++ b/chrome/browser/printing/pdf_nup_converter_client.h
@@ -6,12 +6,10 @@
 #define CHROME_BROWSER_PRINTING_PDF_NUP_CONVERTER_CLIENT_H_
 
 #include <map>
-#include <memory>
 
 #include "chrome/services/printing/public/mojom/pdf_nup_converter.mojom.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "services/service_manager/public/cpp/connector.h"
 
 namespace printing {
 
@@ -57,8 +55,6 @@
 
   mojo::Remote<mojom::PdfNupConverter> CreatePdfNupConverterRemote();
 
-  std::unique_ptr<service_manager::Connector> connector_;
-
   // Stores the mapping between document cookies and their corresponding
   // mojo::Remote.
   std::map<int, mojo::Remote<mojom::PdfNupConverter>> pdf_nup_converter_map_;
diff --git a/chrome/browser/profile_resetter/profile_resetter_unittest.cc b/chrome/browser/profile_resetter/profile_resetter_unittest.cc
index 955219b..19b28a5 100644
--- a/chrome/browser/profile_resetter/profile_resetter_unittest.cc
+++ b/chrome/browser/profile_resetter/profile_resetter_unittest.cc
@@ -20,7 +20,6 @@
 #include "base/test/bind.h"
 #include "base/test/scoped_path_override.h"
 #include "build/build_config.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_base.h"
@@ -32,6 +31,7 @@
 #include "chrome/browser/profile_resetter/resettable_settings_snapshot.h"
 #include "chrome/browser/search/instant_service.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -530,15 +530,13 @@
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
 
   ThemeService* theme_service = ThemeServiceFactory::GetForProfile(profile());
-  content::WindowedNotificationObserver theme_change_observer(
-      chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-      content::Source<ThemeService>(theme_service));
+  test::ThemeServiceChangedWaiter waiter(theme_service);
 
   scoped_refptr<Extension> theme = CreateExtension(
       base::ASCIIToUTF16("example1"), temp_dir.GetPath(), Manifest::UNPACKED,
       extensions::Manifest::TYPE_THEME, false);
   service_->FinishInstallationForTest(theme.get());
-  theme_change_observer.Wait();
+  waiter.WaitForThemeChanged();
 
   EXPECT_FALSE(theme_service->UsingDefaultTheme());
 
@@ -619,15 +617,13 @@
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
 
   ThemeService* theme_service = ThemeServiceFactory::GetForProfile(profile());
-  content::WindowedNotificationObserver theme_change_observer(
-      chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-      content::Source<ThemeService>(theme_service));
+  test::ThemeServiceChangedWaiter waiter(theme_service);
 
   scoped_refptr<Extension> ext1 = CreateExtension(
       base::ASCIIToUTF16("example1"), temp_dir.GetPath(), Manifest::UNPACKED,
       extensions::Manifest::TYPE_THEME, false);
   service_->FinishInstallationForTest(ext1.get());
-  theme_change_observer.Wait();
+  waiter.WaitForThemeChanged();
 
   EXPECT_FALSE(theme_service->UsingDefaultTheme());
 
diff --git a/chrome/browser/profiles/profile_theme_update_service.cc b/chrome/browser/profiles/profile_theme_update_service.cc
index 16c153f..804d8bb 100644
--- a/chrome/browser/profiles/profile_theme_update_service.cc
+++ b/chrome/browser/profiles/profile_theme_update_service.cc
@@ -6,7 +6,6 @@
 
 #include "base/notreached.h"
 #include "base/optional.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
@@ -27,13 +26,14 @@
     : profile_(profile),
       profile_attributes_storage_(profile_attributes_storage),
       theme_service_(theme_service) {
-  notification_registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-                              content::NotificationService::AllSources());
+  theme_service_->AddObserver(this);
   // Kicks off an update on startup.
   UpdateProfileThemeColors();
 }
 
-ProfileThemeUpdateService::~ProfileThemeUpdateService() = default;
+ProfileThemeUpdateService::~ProfileThemeUpdateService() {
+  theme_service_->RemoveObserver(this);
+}
 
 void ProfileThemeUpdateService::UpdateProfileThemeColors() {
   ProfileAttributesEntry* entry =
@@ -54,16 +54,6 @@
   entry->SetProfileThemeColors(colors);
 }
 
-// content::NotificationObserver:
-void ProfileThemeUpdateService::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  switch (type) {
-    case chrome::NOTIFICATION_BROWSER_THEME_CHANGED:
-      UpdateProfileThemeColors();
-      break;
-    default:
-      NOTREACHED();
-  }
+void ProfileThemeUpdateService::OnThemeChanged() {
+  UpdateProfileThemeColors();
 }
diff --git a/chrome/browser/profiles/profile_theme_update_service.h b/chrome/browser/profiles/profile_theme_update_service.h
index 2a8c2b5..5cc40e2 100644
--- a/chrome/browser/profiles/profile_theme_update_service.h
+++ b/chrome/browser/profiles/profile_theme_update_service.h
@@ -5,14 +5,8 @@
 #ifndef CHROME_BROWSER_PROFILES_PROFILE_THEME_UPDATE_SERVICE_H_
 #define CHROME_BROWSER_PROFILES_PROFILE_THEME_UPDATE_SERVICE_H_
 
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "components/keyed_service/core/keyed_service.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
-
-namespace content {
-class NotificationSource;
-class NotificationDetails;
-}  // namespace content
 
 class Profile;
 class ProfileAttributesStorage;
@@ -24,7 +18,7 @@
 // These colors are used to display a list of profiles. They are cached to be
 // accessible without having to load the profiles from disk.
 class ProfileThemeUpdateService : public KeyedService,
-                                  public content::NotificationObserver {
+                                  public ThemeServiceObserver {
  public:
   ProfileThemeUpdateService(
       Profile* profile,
@@ -37,10 +31,8 @@
   ProfileThemeUpdateService& operator=(const ProfileThemeUpdateService&) =
       delete;
 
-  // content::NotificationObserver:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  // ThemeServiceObserver:
+  void OnThemeChanged() override;
 
  private:
   // Updates profile theme colors in |profile_attributes_storage_| for
@@ -50,8 +42,6 @@
   Profile* const profile_;
   ProfileAttributesStorage* const profile_attributes_storage_;
   ThemeService* const theme_service_;
-
-  content::NotificationRegistrar notification_registrar_;
 };
 
 #endif  // CHROME_BROWSER_PROFILES_PROFILE_THEME_UPDATE_SERVICE_H_
diff --git a/chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer.cc b/chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer.cc
index c6e5d62c..78a916a 100644
--- a/chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer.cc
+++ b/chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer.cc
@@ -7,12 +7,11 @@
 #include <memory>
 
 #include "base/strings/utf_string_conversions.h"
-#include "base/time/time.h"
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/renderer_context_menu/render_view_context_menu_proxy.h"
 #include "components/shared_highlighting/core/common/disabled_sites.h"
-#include "components/shared_highlighting/core/common/features.h"
 #include "components/shared_highlighting/core/common/shared_highlighting_metrics.h"
 #include "content/public/browser/context_menu_params.h"
 #include "content/public/browser/render_view_host.h"
@@ -24,9 +23,6 @@
 
 namespace {
 constexpr char kTextFragmentUrlClassifier[] = "#:~:text=";
-
-// Indicates how long context menu should wait for link generation result.
-constexpr base::TimeDelta kTimeoutMs = base::TimeDelta::FromMilliseconds(500);
 }
 
 // static
@@ -63,14 +59,8 @@
       IDC_CONTENT_CONTEXT_COPYLINKTOTEXT,
       l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_COPYLINKTOTEXT));
 
-  if (ShouldPreemptivelyGenerateLink()) {
+  if (ShouldPreemptivelyGenerateLink())
     RequestLinkGeneration();
-    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE,
-        base::BindOnce(&CopyLinkToTextMenuObserver::Timeout,
-                       weak_ptr_factory_.GetWeakPtr()),
-        kTimeoutMs);
-  }
 }
 
 bool CopyLinkToTextMenuObserver::IsCommandIdSupported(int command_id) {
@@ -102,22 +92,22 @@
 
 void CopyLinkToTextMenuObserver::OnRequestLinkGenerationCompleted(
     const std::string& selector) {
-  if (selector.empty())
-    generated_link_ = url_.spec();
-  else
-    generated_link_ = url_.spec() + kTextFragmentUrlClassifier + selector;
-
   if (ShouldPreemptivelyGenerateLink()) {
     if (selector.empty()) {
       // If there is no valid selector, leave the item disabled.
       return;
     }
+    generated_link_ = url_.spec() + kTextFragmentUrlClassifier + selector;
     proxy_->UpdateMenuItem(
         IDC_CONTENT_CONTEXT_COPYLINKTOTEXT, true, false,
         l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_COPYLINKTOTEXT));
     return;
   }
 
+  if (selector.empty())
+    generated_link_ = url_.spec();
+  else
+    generated_link_ = url_.spec() + kTextFragmentUrlClassifier + selector;
   CopyLinkToClipboard();
 }
 
@@ -127,8 +117,7 @@
 }
 
 bool CopyLinkToTextMenuObserver::ShouldPreemptivelyGenerateLink() {
-  return base::FeatureList::IsEnabled(
-      features::kPreemptiveLinkToTextGeneration);
+  return base::FeatureList::IsEnabled(features::kPreemtiveLinkToTextGeneration);
 }
 
 void CopyLinkToTextMenuObserver::RequestLinkGeneration() {
@@ -163,11 +152,11 @@
 
   // Make a call to the renderer to generate a string that uniquely represents
   // the selected text and any context around the text to distinguish it from
-  // the rest of the contents. Get will call a callback with
+  // the rest of the contents. GenerateSelector will call a callback with
   // the generated string if it succeeds or an empty string if it fails.
   main_frame->GetRemoteInterfaces()->GetInterface(
       remote_.BindNewPipeAndPassReceiver());
-  remote_->RequestSelector(base::BindOnce(
+  remote_->GenerateSelector(base::BindOnce(
       &CopyLinkToTextMenuObserver::OnRequestLinkGenerationCompleted,
       weak_ptr_factory_.GetWeakPtr()));
 }
@@ -177,13 +166,3 @@
                                 std::move(data_transfer_endpoint_));
   scw.WriteText(base::UTF8ToUTF16(generated_link_.value()));
 }
-
-void CopyLinkToTextMenuObserver::Timeout() {
-  DCHECK(ShouldPreemptivelyGenerateLink());
-  if (generated_link_.has_value())
-    return;
-  remote_->Cancel();
-  remote_.reset();
-  shared_highlighting::LogGenerateErrorTimeout();
-  OnRequestLinkGenerationCompleted(std::string());
-}
diff --git a/chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer.h b/chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer.h
index 5ea400f..d267591 100644
--- a/chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer.h
+++ b/chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer.h
@@ -54,16 +54,12 @@
   // Copies the generated link to the user's clipboard.
   void CopyLinkToClipboard();
 
-  // Cancels link generation if we are still waiting for it.
-  void Timeout();
-
   mojo::Remote<blink::mojom::TextFragmentSelectorProducer> remote_;
   RenderViewContextMenuProxy* proxy_;
   GURL url_;
   base::Optional<std::string> generated_link_;
   base::Optional<std::string> generated_selector_for_testing_;
   std::unique_ptr<ui::DataTransferEndpoint> data_transfer_endpoint_;
-
   base::WeakPtrFactory<CopyLinkToTextMenuObserver> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer_interactive_uitest.cc b/chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer_interactive_uitest.cc
index 3c7878d8..ea77b4c 100644
--- a/chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer_interactive_uitest.cc
+++ b/chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer_interactive_uitest.cc
@@ -2,19 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/renderer_context_menu/copy_link_to_text_menu_observer.h"
 
 #include "base/macros.h"
 
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/renderer_context_menu/mock_render_view_context_menu.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/test/base/chrome_test_utils.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
-#include "components/shared_highlighting/core/common/features.h"
+#include "components/shared_highlighting/core/common/shared_highlighting_features.h"
 #include "content/public/browser/context_menu_params.h"
 #include "content/public/test/browser_test.h"
 #include "net/dns/mock_host_resolver.h"
@@ -30,16 +31,18 @@
   CopyLinkToTextMenuObserverTest();
 
   void SetUp() override {
-    InProcessBrowserTest::SetUp();
     base::test::ScopedFeatureList scoped_feature_list;
-
     if (GetParam()) {
-      scoped_feature_list.InitAndEnableFeature(
-          features::kPreemptiveLinkToTextGeneration);
+      scoped_feature_list.InitWithFeatures(
+          {features::kPreemtiveLinkToTextGeneration,
+           shared_highlighting::kSharedHighlightingUseBlocklist},
+          {});
     } else {
-      scoped_feature_list.InitAndDisableFeature(
-          features::kPreemptiveLinkToTextGeneration);
+      scoped_feature_list.InitWithFeatures(
+          {shared_highlighting::kSharedHighlightingUseBlocklist},
+          {features::kPreemtiveLinkToTextGeneration});
     }
+    InProcessBrowserTest::SetUp();
   }
 
   void SetUpOnMainThread() override {
@@ -73,10 +76,7 @@
     observer_->InitMenu(params);
   }
 
-  bool ShouldPreemptivelyGenerateLink() {
-    return base::FeatureList::IsEnabled(
-        features::kPreemptiveLinkToTextGeneration);
-  }
+  bool ShouldPreemptivelyGenerateLink() { return GetParam(); }
 
   ~CopyLinkToTextMenuObserverTest() override;
   MockRenderViewContextMenu* menu() { return menu_.get(); }
@@ -97,6 +97,7 @@
   content::ContextMenuParams params;
   params.page_url = GURL("http://foo.com/");
   params.selection_text = base::UTF8ToUTF16("hello world");
+  observer()->OverrideGeneratedSelectorForTesting(std::string());
   InitMenu(params);
   EXPECT_EQ(1u, menu()->GetMenuSize());
   MockRenderViewContextMenu::MockMenuItem item;
@@ -132,14 +133,19 @@
   content::ContextMenuParams params;
   params.page_url = GURL("http://foo.com/");
   params.selection_text = base::UTF8ToUTF16("hello world");
-  observer()->OverrideGeneratedSelectorForTesting("");
+  observer()->OverrideGeneratedSelectorForTesting(std::string());
   InitMenu(params);
-  menu()->ExecuteCommand(IDC_CONTENT_CONTEXT_COPYLINKTOTEXT, 0);
 
-  ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
-  base::string16 text;
-  clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, nullptr, &text);
-  EXPECT_EQ(base::UTF8ToUTF16("http://foo.com/"), text);
+  if (ShouldPreemptivelyGenerateLink()) {
+    EXPECT_FALSE(
+        menu()->IsCommandIdEnabled(IDC_CONTENT_CONTEXT_COPYLINKTOTEXT));
+  } else {
+    menu()->ExecuteCommand(IDC_CONTENT_CONTEXT_COPYLINKTOTEXT, 0);
+    ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
+    base::string16 text;
+    clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, nullptr, &text);
+    EXPECT_EQ(base::UTF8ToUTF16("http://foo.com/"), text);
+  }
 }
 
 IN_PROC_BROWSER_TEST_P(CopyLinkToTextMenuObserverTest, ReplacesRefInURL) {
@@ -179,12 +185,17 @@
   params.page_url = main_url;
   params.selection_text = base::UTF8ToUTF16("hello world");
   InitMenu(params);
-  menu()->ExecuteCommand(IDC_CONTENT_CONTEXT_COPYLINKTOTEXT, 0);
 
-  ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
-  base::string16 text;
-  clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, nullptr, &text);
-  EXPECT_EQ(base::UTF8ToUTF16(main_url.spec()), text);
+  if (ShouldPreemptivelyGenerateLink()) {
+    EXPECT_FALSE(
+        menu()->IsCommandIdEnabled(IDC_CONTENT_CONTEXT_COPYLINKTOTEXT));
+  } else {
+    menu()->ExecuteCommand(IDC_CONTENT_CONTEXT_COPYLINKTOTEXT, 0);
+    ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
+    base::string16 text;
+    clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, nullptr, &text);
+    EXPECT_EQ(base::UTF8ToUTF16(main_url.spec()), text);
+  }
 }
 
 IN_PROC_BROWSER_TEST_P(CopyLinkToTextMenuObserverTest, HiddenForExtensions) {
@@ -201,6 +212,25 @@
   EXPECT_EQ(nullptr, observer);
 }
 
+IN_PROC_BROWSER_TEST_P(CopyLinkToTextMenuObserverTest, Blocklist) {
+  content::BrowserTestClipboardScope test_clipboard_scope;
+  content::ContextMenuParams params;
+  params.page_url = GURL("http://facebook.com/my-profile");
+  params.selection_text = base::UTF8ToUTF16("hello world");
+  InitMenu(params);
+
+  if (ShouldPreemptivelyGenerateLink()) {
+    EXPECT_FALSE(
+        menu()->IsCommandIdEnabled(IDC_CONTENT_CONTEXT_COPYLINKTOTEXT));
+  } else {
+    menu()->ExecuteCommand(IDC_CONTENT_CONTEXT_COPYLINKTOTEXT, 0);
+    ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
+    base::string16 text;
+    clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, nullptr, &text);
+    EXPECT_EQ(base::UTF8ToUTF16("http://facebook.com/my-profile"), text);
+  }
+}
+
 INSTANTIATE_TEST_SUITE_P(All,
                          CopyLinkToTextMenuObserverTest,
                          ::testing::Values(true, false));
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index 130ecdd..d5db103d 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -105,6 +105,7 @@
         "commander:closure_compile",
         "discards:closure_compile",
         "download_internals:closure_compile",
+        "download_shelf:closure_compile",
         "downloads:closure_compile",
         "feedback_webui/js:closure_compile",
         "gaia_auth_host:closure_compile",
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/autoclick/autoclick_test.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/autoclick/autoclick_test.js
index 227ef41..b805e0d 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/autoclick/autoclick_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/autoclick/autoclick_test.js
@@ -77,10 +77,10 @@
               node.location.left + 1, node.location.top + 1, resolve);
         });
         const expected = node.root.location;
+        const focusRings = this.mockAccessibilityPrivate.getFocusRings();
         this.assertSameRect(
             this.mockAccessibilityPrivate.getScrollableBounds(), expected);
-        this.assertSameRect(
-            this.mockAccessibilityPrivate.getFocusRings()[0], expected);
+        this.assertSameRect(focusRings[0].rects[0], expected);
       });
 });
 
@@ -105,10 +105,10 @@
         // text, is scrollable.
         assertTrue(node.parent.parent.scrollable);
         const expected = node.parent.parent.location;
+        const focusRings = this.mockAccessibilityPrivate.getFocusRings();
         this.assertSameRect(
             this.mockAccessibilityPrivate.getScrollableBounds(), expected);
-        this.assertSameRect(
-            this.mockAccessibilityPrivate.getFocusRings()[0], expected);
+        this.assertSameRect(focusRings[0].rects[0], expected);
       });
 });
 
@@ -138,10 +138,10 @@
               node.location.left + 1, node.location.top + 1, resolve);
         });
         const expected = node.root.location;
+        const focusRings = this.mockAccessibilityPrivate.getFocusRings();
         this.assertSameRect(
             this.mockAccessibilityPrivate.getScrollableBounds(), expected);
-        this.assertSameRect(
-            this.mockAccessibilityPrivate.getFocusRings()[0], expected);
+        this.assertSameRect(focusRings[0].rects[0], expected);
       });
 });
 
diff --git a/chrome/browser/resources/chromeos/accessibility/common/testing/mock_accessibility_private.js b/chrome/browser/resources/chromeos/accessibility/common/testing/mock_accessibility_private.js
index afb4d5b..68853d3 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/testing/mock_accessibility_private.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/testing/mock_accessibility_private.js
@@ -2,6 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+/**
+ * @typedef {{
+ *   show: boolean,
+ *   anchor: (!chrome.accessibilityPrivate.ScreenRect|undefined),
+ *   isPaused: (boolean|undefined),
+ *   speed: (number|undefined),
+ * }}
+ */
+let SelectToSpeakPanelState;
+
 /*
  * A mock AccessibilityPrivate API for tests.
  */
@@ -13,14 +23,29 @@
   /** @private {function<number, number>} */
   boundsListener_: null,
 
+  /**
+   * @private {function(!chrome.accessibilityPrivate.SelectToSpeakPanelAction,
+   *     number=)}
+   */
+  selectToSpeakPanelActionListener_: null,
+
+  /** @private {function()} */
+  selectToSpeakStateChangeListener_: null,
+
   /** @private {!chrome.accessibilityPrivate.ScreenRect} */
   scrollableBounds_: {},
 
-  /** @private {!Array<!chrome.accessibilityPrivate.ScreenRect>} */
-  focusRingRects_: [],
+  /** @private {!Array<!chrome.accessibilityPrivate.FocusRingInfo>} */
+  focusRings_: [],
   handleScrollableBoundsForPointFoundCallback_: null,
   moveMagnifierToRectCallback_: null,
 
+  /** @private {?SelectToSpeakPanelState} */
+  selectToSpeakPanelState_: null,
+
+  /** @private {!Array<!chrome.accessibilityPrivate.ScreenRect>} */
+  highlightRects_: [],
+
   // Methods from AccessibilityPrivate API. //
 
   onScrollableBoundsForPointRequested: {
@@ -42,6 +67,27 @@
     }
   },
 
+  onSelectToSpeakPanelAction: {
+    /**
+     * Adds a listener to onSelectToSpeakPanelAction.
+     * @param {!function(!chrome.accessibilityPrivate.SelectToSpeakPanelAction,
+     *     number=)} listener
+     */
+    addListener: (listener) => {
+      MockAccessibilityPrivate.selectToSpeakPanelActionListener_ = listener;
+    },
+  },
+
+  onSelectToSpeakStateChangeRequested: {
+    /**
+     * Adds a listener to onSelectToSpeakStateChangeRequested.
+     * @param {!function()} listener
+     */
+    addListener: (listener) => {
+      MockAccessibilityPrivate.selectToSpeakStateChangeListener_ = listener;
+    },
+  },
+
   /**
    * Called when AccessibilityCommon finds scrollable bounds at a point.
    * @param {!chrome.accessibilityPrivate.ScreenRect} bounds
@@ -63,24 +109,45 @@
   },
 
   /**
-   * Called when AccessibilityCommon wants to set the focus rings. We can assume
-   * that it is only setting one set of rings at a time, and safely extract
-   * focusRingInfos[0].rects.
-   * @param {!Array<!FocusRingInfo>} focusRingInfos
+   * Called when AccessibilityCommon wants to set the focus rings. We can
+   * assume that it is only setting one set of rings at a time, and safely
+   * extract focusRingInfos[0].rects.
+   * @param {!Array<!!chrome.accessibilityPrivate.FocusRingInfo>} focusRingInfos
    */
   setFocusRings: (focusRingInfos) => {
-    MockAccessibilityPrivate.focusRingRects_ = focusRingInfos[0].rects;
+    MockAccessibilityPrivate.focusRings_ = focusRingInfos;
+  },
+
+  /**
+   * Sets highlights.
+   * @param {!Array<!chrome.accessibilityPrivate.ScreenRect>} rects
+   * @param {string} color
+   */
+  setHighlights: (rects, color) => {
+    MockAccessibilityPrivate.highlightRects_ = rects;
+  },
+
+  /**
+   * Updates properties of the Select-to-speak panel.
+   * @param {boolean} show
+   * @param {!chrome.accessibilityPrivate.ScreenRect=} anchor
+   * @param {boolean=} isPaused
+   * @param {number=} speed
+   */
+  updateSelectToSpeakPanel: (show, anchor, isPaused, speed) => {
+    MockAccessibilityPrivate
+        .selectToSpeakPanelState_ = {show, anchor, isPaused, speed};
   },
 
   // Methods for testing. //
 
   /**
-   * Called to get the AccessibilityCommon extension to use the Automation API
-   * to find the scrollable bounds at a point. In Automatic Clicks, this would
-   * actually be initiated by ash/autoclick/autoclick_controller calling the
-   * AccessibilityPrivate API call.
-   * When the bounds are found, handleScrollableBoundsForPointFoundCallback will
-   * be called to inform the test that work is complete.
+   * Called to get the AccessibilityCommon extension to use the Automation
+   * API to find the scrollable bounds at a point. In Automatic Clicks, this
+   * would actually be initiated by ash/autoclick/autoclick_controller
+   * calling the AccessibilityPrivate API call. When the bounds are found,
+   * handleScrollableBoundsForPointFoundCallback will be called to inform
+   * the test that work is complete.
    * @param {number} x
    * @param {number} y
    * @param {!function<>} handleScrollableBoundsForPointFoundCallback
@@ -115,9 +182,44 @@
   /**
    * Gets the focus rings bounds which were set by the AccessibilityCommon
    * extension.
-   * @return {Array<!chrome.AccessibilityPrivate.ScreenRect>}
+   * @return {Array<!chrome.accessibilityPrivate.FocusRingInfo>}
    */
   getFocusRings: () => {
-    return MockAccessibilityPrivate.focusRingRects_;
+    return MockAccessibilityPrivate.focusRings_;
+  },
+
+  /**
+   * Gets the highlight bounds.
+   * @return {!Array<!chrome.AccessibilityPrivate.ScreenRect>}
+   */
+  getHighlights: () => {
+    return MockAccessibilityPrivate.highlightRects_;
+  },
+
+  /**
+   * @return {?SelectToSpeakPanelState}
+   */
+  getSelectToSpeakPanelState: () => {
+    return MockAccessibilityPrivate.selectToSpeakPanelState_;
+  },
+
+  /**
+   * Simulates Select-to-speak panel action.
+   * @param {!chrome.accessibilityPrivate.SelectToSpeakPanelAction} action
+   * @param {number=} value
+   */
+  sendSelectToSpeakPanelAction(action, value) {
+    if (MockAccessibilityPrivate.selectToSpeakPanelActionListener_) {
+      MockAccessibilityPrivate.selectToSpeakPanelActionListener_(action, value);
+    }
+  },
+
+  /**
+   * Simulates Select-to-speak state change request (tray button).
+   */
+  sendSelectToSpeakStateChangeRequest() {
+    if (MockAccessibilityPrivate.selectToSpeakStateChangeListener_) {
+      MockAccessibilityPrivate.selectToSpeakStateChangeListener_();
+    }
   },
 };
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn
index d6f4f62..7316480 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn
@@ -31,6 +31,7 @@
     "earcons/null_selection.ogg",
     "input_handler.js",
     "metrics_utils.js",
+    "node_navigation_utils.js",
     "node_utils.js",
     "options.css",
     "options.html",
@@ -42,6 +43,7 @@
     "select_to_speak_main.js",
     "select_to_speak_options.js",
     "sentence_utils.js",
+    "ui_manager.js",
     "unchecked.png",
     "word_utils.js",
   ]
@@ -88,10 +90,12 @@
 
   # These are unit tests run under an extension environment to get ES6 module support.
   sources += [
+    "node_navigation_utils_unittest.js",
     "node_utils_unittest.js",
     "paragraph_utils_unittest.js",
     "select_to_speak_unittest.js",
     "sentence_utils_unittest.js",
+    "ui_manager_unittest.js",
     "word_utils_unittest.js",
   ]
   gen_include_files = [
@@ -122,6 +126,7 @@
   deps = [
     ":input_handler",
     ":metrics_utils",
+    ":node_navigation_utils",
     ":node_utils",
     ":paragraph_utils",
     ":prefs_manager",
@@ -129,6 +134,7 @@
     ":select_to_speak_constants",
     ":select_to_speak_options",
     ":sentence_utils",
+    ":ui_manager",
     ":word_utils",
     "../common:automation_predicate",
     "../common:automation_util",
@@ -144,10 +150,12 @@
   deps = [
     ":input_handler",
     ":metrics_utils",
+    ":node_navigation_utils",
     ":node_utils",
     ":paragraph_utils",
     ":prefs_manager",
     ":sentence_utils",
+    ":ui_manager",
     ":word_utils",
     "../common:automation_util",
     "../common:constants",
@@ -173,6 +181,14 @@
   ]
 }
 
+js_library("node_navigation_utils") {
+  deps = [
+    ":node_utils",
+    ":paragraph_utils",
+  ]
+  externs_list = [ "$externs_path/automation.js" ]
+}
+
 js_library("node_utils") {
   deps = [
     ":paragraph_utils",
@@ -214,3 +230,11 @@
 
 js_library("select_to_speak_constants") {
 }
+
+js_library("ui_manager") {
+  deps = [
+    ":paragraph_utils",
+    ":prefs_manager",
+  ]
+  externs_list = [ "$externs_path/accessibility_private.js" ]
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_navigation_utils.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_navigation_utils.js
new file mode 100644
index 0000000..4a1a499
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_navigation_utils.js
@@ -0,0 +1,296 @@
+// 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 {NodeUtils} from './node_utils.js';
+import {ParagraphUtils} from './paragraph_utils.js';
+import {SentenceUtils} from './sentence_utils.js';
+
+const AutomationNode = chrome.automation.AutomationNode;
+
+/**
+ * A predicate for paragraph selection. The predicate examines an array of nodes
+ * and returns false if the paragraph should not be used.
+ * @typedef {function(Array<!AutomationNode>): boolean}
+ */
+let ParagraphPred;
+
+/**
+ * Utility functions to handle sentence navigation and paragraph navigation.
+ * TODO(crbug.com/1168644): move other navigation-only utility functions to this
+ * file.
+ */
+export class NodeNavigationUtils {
+  constructor() {}
+
+  /**
+   * Finds the nodes for the next text block in the given direction. This
+   * function is based on |NodeUtils.getNextParagraph| but provides additional
+   * checks on the anchor node used for searchiong. This function also applies a
+   * |pred| to filter out unqualified paragraph.
+   * @param {!ParagraphUtils.NodeGroup|undefined} currentNodeGroup
+   * @param {constants.Dir} direction
+   * @param {ParagraphPred} pred A predicate to apply when selecting the
+   *     paragraph. After querying nodes, the function uses |pred| to determine
+   *     if the queried nodes are a part of a valid paragraph. If |pred| returns
+   *     false, the function returns an empty array. For example, we may want to
+   *     discard nodes from UI components.
+   * @return {Array<!AutomationNode>} A list of nodes for the next block in the
+   *     given direction.
+   */
+  static getNodesForNextParagraph(currentNodeGroup, direction, pred) {
+    if (!currentNodeGroup) {
+      return [];
+    }
+    // Use current block parent as starting point to navigate from. If it is not
+    // a valid block, then use one of the nodes that are currently activated.
+    let node = currentNodeGroup.blockParent;
+    if ((node === null || node.isRootNode || node.role === undefined) &&
+        currentNodeGroup.nodes.length > 0) {
+      node = currentNodeGroup.nodes[0].node;
+    }
+    if (node === null || node.role === undefined) {
+      // Could not find any nodes to navigate from.
+      return [];
+    }
+
+    // Retrieve the nodes that make up the next/prev paragraph.
+    const nextParagraphNodes = NodeUtils.getNextParagraph(node, direction);
+    if (nextParagraphNodes.length === 0 || !pred(nextParagraphNodes)) {
+      // Cannot find any valid nodes in given direction.
+      return [];
+    }
+
+    return nextParagraphNodes;
+  }
+
+  /**
+   * Gets the nodes for the next sentence. First, we search the next sentence in
+   * the current node group. If we do not find one, we will search within the
+   * remaining content in the current paragraph (i.e., text block). If this
+   * still fails, we will search the next paragraph. The current position is
+   * defined by the |currentCharIndex| in the |currentNodeGroup|. When
+   * navigating backwards, we skip the sentence start in the current sentence.
+   * For example, when navigating backward from the middle of the current
+   * sentence, the function returns content from the start of the previous
+   * sentence.
+   * TODO(leileilei@google.com): Handle the edge case where the user navigates
+   * to next sentence from the end of a document, see http://crbug.com/1160962.
+   * @param {!ParagraphUtils.NodeGroup|undefined} currentNodeGroup
+   * @param {number} currentCharIndex
+   * @param {constants.Dir} direction Direction to search for the next sentence.
+   *     If set to forward, we look for the sentence start after the current
+   *     position. Otherwise, we look for the sentence start before the current
+   *     position.
+   * @param {ParagraphPred} pred A predicate to apply when selecting the
+   *     paragraph. See |NodeNavigationUtils.getNodesForNextParagraph| for
+   * details.
+   * @return {!{nodes: Array<!AutomationNode>,
+   *          offset: (number|undefined)}}
+   *     nodes: A list of nodes for the next block in the given direction.
+   *     offset: The start offset for the found sentence.
+   */
+  static getNodesForNextSentence(
+      currentNodeGroup, currentCharIndex, direction, pred) {
+    let nodes = [], offset;
+    if (!currentNodeGroup) {
+      return {nodes, offset};
+    }
+
+    // Sets |skipCurrentSentence| to true for initial backward navigation.
+    let skipCurrentSentence = direction === constants.Dir.BACKWARD;
+
+    // Checks the next sentence within this node group. If we can find the
+    // next sentence that fulfilled the requirements, return that.
+    ({nodes, offset} =
+         NodeNavigationUtils.getNodesForNextSentenceWithinNodeGroup_(
+             currentNodeGroup, currentCharIndex, direction, pred,
+             skipCurrentSentence));
+    if (nodes.length > 0) {
+      return {nodes, offset};
+    }
+
+    // If there is no next sentence at the current node group, look for the
+    // content within this paragraph. First, we get the remaining content in
+    // the paragraph. The returned offset marks the char index of the current
+    // position in the paragraph. When searching forward, the offset is the
+    // char index pointing to the beginning of the remaining content. When
+    // searching backward, the offset is the char index pointing to the char
+    // after the remaining content.
+    ({nodes, offset} = NodeUtils.getNextNodesInParagraphFromNodeGroup(
+         currentNodeGroup, currentCharIndex, direction));
+    // If we have reached to the end of the current paragraph, return the
+    // sentence from the next paragraph.
+    if (nodes.length === 0) {
+      return NodeNavigationUtils.getNodesForNextSentenceInNextParagraph_(
+          currentNodeGroup, direction, pred);
+    }
+    // Get the node group for the remaining content in the paragraph. If we are
+    // looking for the content after the current position, set startIndex as
+    // offset. Otherwise, set endIndex as offset.
+    const startIndex = direction === constants.Dir.FORWARD ? offset : undefined;
+    const endIndex = direction === constants.Dir.FORWARD ? undefined : offset;
+    const {nodeGroup, startIndexInGroup, endIndexInGroup} =
+        ParagraphUtils.buildSingleNodeGroupWithOffset(
+            nodes, startIndex, endIndex);
+    // Search in the remaining content.
+    const charIndex = direction === constants.Dir.FORWARD ? startIndexInGroup :
+                                                            endIndexInGroup;
+    // The charIndex is guaranteed to be valid at this point, although the
+    // closure compiler is not able to detect it as a valid number.
+    if (charIndex === undefined) {
+      console.warn('Navigate sentence with an invalid char index', charIndex);
+      return {nodes: [], offset: undefined};
+    }
+    // When searching backward, we need to adjust |skipCurrentSentence|. The
+    // remaining content we get excludes the char at |currentCharIndex|. If this
+    // char is a sentence start, we have already skipped the current sentence so
+    // we need to change |skipCurrentSentence| to false for the next search.
+    if (direction === constants.Dir.BACKWARD && skipCurrentSentence) {
+      const currentPositionIsSentenceStart =
+          SentenceUtils.isSentenceStart(currentNodeGroup, currentCharIndex);
+      if (currentPositionIsSentenceStart) {
+        skipCurrentSentence = false;
+      }
+    }
+    ({nodes, offset} =
+         NodeNavigationUtils.getNodesForNextSentenceWithinNodeGroup_(
+             nodeGroup, charIndex, direction, pred, skipCurrentSentence));
+    if (nodes.length > 0) {
+      return {nodes, offset};
+    }
+
+    // If there is no next sentence within this paragraph, enqueue the sentence
+    // from the next paragraph.
+    return NodeNavigationUtils.getNodesForNextSentenceInNextParagraph_(
+        currentNodeGroup, direction, pred);
+  }
+
+  /**
+   * Gets the nodes for the next sentence within the |nodeGroup|. If the
+   * |direction| is set to forward, it will navigate to the sentence start after
+   * the |startCharIndex|. Otherwise, it will look for the sentence start before
+   * the |startCharIndex|.
+   * @param {ParagraphUtils.NodeGroup} nodeGroup
+   * @param {number} startCharIndex The char index that we start from. This
+   *     index is relative to the text content of this node group and is
+   *     exclusive: if a sentence start at 0 and we search with a 0
+   *     |startCharIndex|, this function will return the next sentence start
+   *     after 0 if we search forward.
+   * @param {constants.Dir} direction
+   * @param {ParagraphPred} pred A predicate to apply when selecting the
+   *     paragraph. See |NodeNavigationUtils.getNodesForNextParagraph| for
+   * details.
+   * @param {boolean} skipCurrentSentence Whether to skip the current sentence
+   *     when navigating backward. Please refer to more details in the
+   *     |NodeNavigationUtils.getNodesForNextSentence|.
+   * @return {!{nodes: Array<!AutomationNode>,
+   *          offset: (number|undefined)}}
+   *     nodes: A list of nodes for the next block in the given direction.
+   *     offset: The start offset for the found sentence.
+   * @private
+   */
+  static getNodesForNextSentenceWithinNodeGroup_(
+      nodeGroup, startCharIndex, direction, pred, skipCurrentSentence) {
+    if (!nodeGroup) {
+      return {nodes: [], offset: undefined};
+    }
+    let nextSentenceStart =
+        SentenceUtils.getSentenceStart(nodeGroup, startCharIndex, direction);
+    if (nextSentenceStart === null) {
+      return {nodes: [], offset: undefined};
+    }
+    // When we search backward, if we want to skip the current sentence, we
+    // need to search the sentence start in the previous sentence. If the
+    // position of |startCharIndex| is a sentence start, the current
+    // |nextSentenceStart| is already in the previous sentence because
+    // getSentenceStart excludes the search index. Otherwise, the
+    // |nextSentenceStart| we found is the start of current sentence, and we
+    // need to search backward again.
+    if (direction === constants.Dir.BACKWARD && skipCurrentSentence &&
+        !SentenceUtils.isSentenceStart(nodeGroup, startCharIndex)) {
+      nextSentenceStart = SentenceUtils.getSentenceStart(
+          nodeGroup, nextSentenceStart, direction);
+    }
+    // If the second sentence start is not valid, returns empty nodes.
+    if (nextSentenceStart === null) {
+      return {nodes: [], offset: undefined};
+    }
+
+    // Get the content between the sentence start and the end of the paragraph.
+    const {nodes, offset} = NodeUtils.getNextNodesInParagraphFromNodeGroup(
+        nodeGroup, nextSentenceStart, constants.Dir.FORWARD);
+    if (nodes.length === 0) {
+      // There is no remaining content. Move to the next paragraph. This is
+      // unexpected since we already found a sentence start, which indicates
+      // there should be some content to read.
+      return NodeNavigationUtils.getNodesForNextSentenceInNextParagraph_(
+          nodeGroup, direction, pred);
+    }
+
+    return {nodes, offset};
+  }
+
+  /**
+   * Gets the nodes for the next sentence in the next text block in the given
+   * direction. If the |direction| is set to forward, it will navigate to the
+   * start of the following text block. Otherwise, it will look for the last
+   * sentence in the previous text block.
+   * @param {!ParagraphUtils.NodeGroup|undefined} currentNodeGroup
+   * @param {constants.Dir} direction
+   * @param {ParagraphPred} pred A predicate to apply when selecting the
+   *     paragraph. See |NodeNavigationUtils.getNodesForNextParagraph| for
+   * details.
+   * @return {!{nodes: Array<!AutomationNode>,
+   *          offset: (number|undefined)}}
+   *     nodes: A list of nodes for the next block in the given direction.
+   *     offset: The start offset for the found sentence.
+   * @private
+   */
+  static getNodesForNextSentenceInNextParagraph_(
+      currentNodeGroup, direction, pred) {
+    const paragraphNodes = NodeNavigationUtils.getNodesForNextParagraph(
+        currentNodeGroup, direction, pred);
+    // Return early if the nodes are empty.
+    if (paragraphNodes.length === 0) {
+      return {nodes: paragraphNodes, offset: undefined};
+    }
+
+    if (direction === constants.Dir.FORWARD) {
+      // If we are looking for the sentence start in the following text block,
+      // return nodes.
+      return {nodes: paragraphNodes, offset: undefined};
+    }
+
+    // If we are looking for the previous sentence start, search the last
+    // sentence in the previous text block. Get the node group for the previous
+    // text block. The returned startIndexInGroup and endIndexInGroup are
+    // unused.
+    const {nodeGroup, startIndexInGroup, endIndexInGroup} =
+        ParagraphUtils.buildSingleNodeGroupWithOffset(paragraphNodes);
+    // We search backward for the sentence start before the end of the text
+    // block.
+    const searchOffset = nodeGroup.text.length;
+    const sentenceStartIndex = SentenceUtils.getSentenceStart(
+        nodeGroup, searchOffset, constants.Dir.BACKWARD);
+    // If there is no sentence start in the previous text block, return the
+    // nodes of the block.
+    if (sentenceStartIndex === null) {
+      return {nodes: paragraphNodes, offset: undefined};
+    }
+    // Gets the remaining content between the sentence start until the end of
+    // the text block. The offset is the start char index for the first node in
+    // the remaining content.
+    const {nodes, offset} = NodeUtils.getNextNodesInParagraphFromNodeGroup(
+        nodeGroup, sentenceStartIndex, constants.Dir.FORWARD);
+    if (nodes.length === 0) {
+      // If there is no remaining content, return the nodes of the block. This
+      // is unexpected since we already found a sentence start, which indicates
+      // there should be some content to read.
+      return {nodes: paragraphNodes, offset: undefined};
+    }
+    // Returns the remaining content from the sentence start until the end of
+    // the block.
+    return {nodes, offset};
+  }
+}
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_navigation_utils_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_navigation_utils_unittest.js
new file mode 100644
index 0000000..f0b363952
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_navigation_utils_unittest.js
@@ -0,0 +1,363 @@
+// 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.
+
+GEN_INCLUDE(['select_to_speak_e2e_test_base.js']);
+
+/**
+ * Test fixture for navigation_utils.js.
+ */
+SelectToSpeakNodeNavigationUtilsUnitTest = class extends SelectToSpeakE2ETest {
+  /** @override */
+  setUp() {
+    var runTest = this.deferRunTest(WhenTestDone.EXPECT);
+    (async function() {
+      let module = await import('/select_to_speak/node_navigation_utils.js');
+      window.NodeNavigationUtils = module.NodeNavigationUtils;
+
+      module = await import('/select_to_speak/node_utils.js');
+      window.NodeUtils = module.NodeUtils;
+
+      module = await import('/select_to_speak/paragraph_utils.js');
+      window.ParagraphUtils = module.ParagraphUtils;
+
+      module = await import('/select_to_speak/sentence_utils.js');
+      runTest();
+    })();
+  }
+};
+
+SYNC_TEST_F(
+    'SelectToSpeakNodeNavigationUtilsUnitTest', 'GetNodesForNextParagraph',
+    function() {
+      const root = createMockNode({role: 'rootWebArea'});
+      const paragraph1 = createMockNode(
+          {role: 'paragraph', display: 'block', parent: root, root});
+      const text1 = createMockNode(
+          {role: 'staticText', parent: paragraph1, root, name: 'Line 1'});
+      const text2 = createMockNode(
+          {role: 'staticText', parent: paragraph1, root, name: 'Line 2'});
+      const paragraph2 = createMockNode(
+          {role: 'paragraph', display: 'block', parent: root, root});
+      const text3 = createMockNode(
+          {role: 'staticText', parent: paragraph2, root, name: 'Line 3'});
+
+      const nodeGroupForParagraph1 = ParagraphUtils.buildNodeGroup(
+          [text1, text2], 0 /* index */, {splitOnLanguage: false});
+      const nodeGroupForParagraph2 = ParagraphUtils.buildNodeGroup(
+          [text3], 0 /* index */, {splitOnLanguage: false});
+
+      // Navigating forward from paragraph 1 returns the nodes of paragraph 2.
+      let result = NodeNavigationUtils.getNodesForNextParagraph(
+          nodeGroupForParagraph1, constants.Dir.FORWARD,
+          () => true /* does not filter any paragraph */);
+      assertEquals(result.length, 1);
+      assertEquals(result[0], text3);
+
+      // Navigating backward from paragraph 1 returns no nodes.
+      result = NodeNavigationUtils.getNodesForNextParagraph(
+          nodeGroupForParagraph1, constants.Dir.BACKWARD,
+          () => true /* does not filter any paragraph */);
+      assertEquals(result.length, 0);
+
+      // Navigating backward from paragraph 2 returns the nodes of paragraph 1.
+      result = NodeNavigationUtils.getNodesForNextParagraph(
+          nodeGroupForParagraph2, constants.Dir.BACKWARD,
+          () => true /* does not filter any paragraph */);
+      assertEquals(result.length, 2);
+      assertEquals(result[0], text1);
+      assertEquals(result[1], text2);
+
+      // Navigating forward from paragraph 2 returns no nodes.
+      result = NodeNavigationUtils.getNodesForNextParagraph(
+          nodeGroupForParagraph2, constants.Dir.FORWARD,
+          () => true /* does not filter any paragraph */);
+      assertEquals(result.length, 0);
+
+      // Navigates forward from paragraph 1 with a pred that filters out
+      // paragraph 2.
+      result = NodeNavigationUtils.getNodesForNextParagraph(
+          nodeGroupForParagraph1, constants.Dir.FORWARD,
+          (nodes) => !(nodes.find(
+              n => n.parent ===
+                  paragraph2) /* filter out nodes belong to paragraph 2 */));
+      assertEquals(result.length, 0);
+    });
+
+SYNC_TEST_F(
+    'SelectToSpeakNodeNavigationUtilsUnitTest', 'GetNodesForNextSentence',
+    function() {
+      const root = createMockNode({role: 'rootWebArea'});
+      const paragraph1 = createMockNode(
+          {role: 'paragraph', display: 'block', parent: root, root});
+      const text1 = createMockNode({
+        role: 'staticText',
+        parent: paragraph1,
+        root,
+        name: 'Line 1.',
+        sentenceStarts: [0]
+      });
+      const text2 = createMockNode({
+        role: 'staticText',
+        parent: paragraph1,
+        root,
+        name: 'Line 2.',
+        sentenceStarts: [0]
+      });
+      const paragraph2 = createMockNode(
+          {role: 'paragraph', display: 'block', parent: root, root});
+      const text3 = createMockNode({
+        role: 'staticText',
+        parent: paragraph2,
+        root,
+        name: 'Line 3.',
+        sentenceStarts: [0]
+      });
+      const text4 = createMockNode({
+        role: 'staticText',
+        parent: paragraph2,
+        root,
+        name: 'Line 4.',
+        sentenceStarts: [0]
+      });
+      const nodeGroupForParagraph1 = ParagraphUtils.buildNodeGroup(
+          [text1, text2], 0 /* index */, {splitOnLanguage: false});
+      const nodeGroupForParagraph2 = ParagraphUtils.buildNodeGroup(
+          [text3, text4], 0 /* index */, {splitOnLanguage: false});
+
+      // First paragraph has two sentences.
+      assertEquals(nodeGroupForParagraph1.text, 'Line 1. Line 2. ');
+      // Second paragraph has another two sentences.
+      assertEquals(nodeGroupForParagraph2.text, 'Line 3. Line 4. ');
+
+      let nodes, offset;
+      // Navigating forward from the first sentence returns the second sentence.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroupForParagraph1, 0 /* currentCharIndex */,
+           constants.Dir.FORWARD,
+           () => true /* does not filter any paragraph */));
+      assertEquals(nodes.length, 1);
+      assertEquals(nodes[0], text2);
+      assertEquals(offset, 0);
+
+      // Navigating forward from the second sentence returns the second
+      // paragraph. Index 8 points to the word "Line" in the second sentence.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroupForParagraph1, 8 /* currentCharIndex */,
+           constants.Dir.FORWARD,
+           () => true /* does not filter any paragraph */));
+      assertEquals(nodes.length, 2);
+      assertEquals(nodes[0], text3);
+      assertEquals(nodes[1], text4);
+      assertEquals(offset, undefined);
+
+      // Navigating forward from the third sentence returns the fourth
+      // sentence. Index 4 points to the word "3" in the third sentence.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroupForParagraph2, 4 /* currentCharIndex */,
+           constants.Dir.FORWARD,
+           () => true /* does not filter any paragraph */));
+      assertEquals(nodes.length, 1);
+      assertEquals(nodes[0], text4);
+      assertEquals(offset, 0);
+
+      // Navigating forward from the fourth sentence returns an empty result.
+      // Index 8 points to the word "Line" in the fourth sentence.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroupForParagraph2, 8 /* currentCharIndex */,
+           constants.Dir.FORWARD,
+           () => true /* does not filter any paragraph */));
+      assertEquals(nodes.length, 0);
+
+      // Navigates forward from second sentence with a pred that filters out
+      // paragraph 2. Index 8 points to the word "Line" in the second sentence.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroupForParagraph1, 8 /* currentCharIndex */,
+           constants.Dir.FORWARD,
+           (nodes) => !(nodes.find(
+               n => n.parent ===
+                   paragraph2) /* filter out nodes belong to paragraph 2 */)));
+      assertEquals(nodes.length, 0);
+
+      // Navigating backward from the first sentence returns an empty result.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroupForParagraph1, 0 /* currentCharIndex */,
+           constants.Dir.BACKWARD,
+           () => true /* does not filter any paragraph */));
+      assertEquals(nodes.length, 0);
+
+      // Navigating backward from the second sentence returns the first
+      // paragraph. Index 8 points to the word "Line" in the second sentence.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroupForParagraph1, 8 /* currentCharIndex */,
+           constants.Dir.BACKWARD,
+           () => true /* does not filter any paragraph */));
+      assertEquals(nodes.length, 2);
+      assertEquals(nodes[0], text1);
+      assertEquals(nodes[1], text2);
+      assertEquals(offset, 0);
+
+      // Navigating backward from the third sentence returns the second
+      // sentence. Index 4 points to the word "3" in the third sentence.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroupForParagraph2, 4 /* currentCharIndex */,
+           constants.Dir.BACKWARD,
+           () => true /* does not filter any paragraph */));
+      assertEquals(nodes.length, 1);
+      assertEquals(nodes[0], text2);
+      assertEquals(offset, 0);
+
+      // Navigating backward from the fourth sentence returns the second
+      // paragraph. Index 8 points to the word "Line" in the fourth sentence.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroupForParagraph2, 8 /* currentCharIndex */,
+           constants.Dir.BACKWARD,
+           () => true /* does not filter any paragraph */));
+      assertEquals(nodes.length, 2);
+      assertEquals(nodes[0], text3);
+      assertEquals(nodes[1], text4);
+      assertEquals(offset, 0);
+
+      // Navigates backward from third sentence with a pred that filters out
+      // paragraph 1.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroupForParagraph2, 0 /* currentCharIndex */,
+           constants.Dir.BACKWARD,
+           (nodes) => !(nodes.find(
+               n => n.parent ===
+                   paragraph1) /* filter out nodes belong to paragraph 1 */)));
+      assertEquals(nodes.length, 0);
+    });
+
+SYNC_TEST_F(
+    'SelectToSpeakNodeNavigationUtilsUnitTest',
+    'GetNodesForNextSentenceWithChoppedNodes', function() {
+      const root = createMockNode({role: 'rootWebArea'});
+      const paragraph1 = createMockNode(
+          {role: 'paragraph', display: 'block', parent: root, root});
+      const text1 = createMockNode({
+        role: 'staticText',
+        parent: paragraph1,
+        root,
+        name: 'Line 1.',
+        sentenceStarts: [0]
+      });
+      const text2 = createMockNode({
+        role: 'staticText',
+        parent: paragraph1,
+        root,
+        name: 'This sentence',
+        sentenceStarts: [0]
+      });
+      const text3 = createMockNode({
+        role: 'staticText',
+        parent: paragraph1,
+        root,
+        name: 'is chopped. Another',
+        sentenceStarts: [12]
+      });
+      const text4 = createMockNode({
+        role: 'staticText',
+        parent: paragraph1,
+        root,
+        name: 'chopped',
+        sentenceStarts: []
+      });
+      const text5 = createMockNode({
+        role: 'staticText',
+        parent: paragraph1,
+        root,
+        name: 'sentence.',
+        sentenceStarts: []
+      });
+      const nodeGroup = ParagraphUtils.buildNodeGroup(
+          [text1, text2, text3, text4, text5], 0 /* index */,
+          {splitOnLanguage: false});
+
+      // One paragraph has three sentences.
+      assertEquals(
+          nodeGroup.text,
+          'Line 1. This sentence is chopped. Another chopped sentence. ');
+
+      let nodes, offset, result;
+      // Navigating forward from the first word returns the content starting
+      // from the second sentence.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroup, 0 /* currentCharIndex */, constants.Dir.FORWARD,
+           () => true /* does not filter any paragraph */));
+      result = ParagraphUtils.buildNodeGroup(
+          nodes, 0 /* index */, {splitOnLanguage: false});
+      assertEquals(nodes.length, 4);
+      assertEquals(offset, 0);
+      assertEquals(
+          result.text, 'This sentence is chopped. Another chopped sentence. ');
+
+      // Navigating forward from the third word returns the content starting
+      // from the third sentence. Index 8 points to the word "This" in the
+      // second sentence.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroup, 8 /* currentCharIndex */, constants.Dir.FORWARD,
+           () => true /* does not filter any paragraph */));
+      result = ParagraphUtils.buildNodeGroup(
+          nodes, 0 /* index */, {splitOnLanguage: false});
+      assertEquals(nodes.length, 3);
+      assertEquals(offset, 12);
+      assertEquals(result.text, 'is chopped. Another chopped sentence. ');
+
+      // Navigating backward from the third sentence returns the content
+      // starting from the second sentence. Index 42 points to the word
+      // "chopped" in the third sentence.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroup, 42 /* currentCharIndex */, constants.Dir.BACKWARD,
+           () => true /* does not filter any paragraph */));
+      result = ParagraphUtils.buildNodeGroup(
+          nodes, 0 /* index */, {splitOnLanguage: false});
+      assertEquals(nodes.length, 4);
+      assertEquals(offset, 0);
+      assertEquals(
+          result.text, 'This sentence is chopped. Another chopped sentence. ');
+
+      // Navigating backward from the third sentence returns the content
+      // starting from the second sentence. Index 34 points to the word
+      // "Another" in the third sentence.
+      ({nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+           nodeGroup, 34 /* currentCharIndex */, constants.Dir.BACKWARD,
+           () => true /* does not filter any paragraph */));
+      result = ParagraphUtils.buildNodeGroup(
+          nodes, 0 /* index */, {splitOnLanguage: false});
+      assertEquals(nodes.length, 4);
+      assertEquals(offset, 0);
+      assertEquals(
+          result.text, 'This sentence is chopped. Another chopped sentence. ');
+    });
+
+/**
+ * Creates a AutomationNode-like object.
+ * @param {!Object} properties
+ */
+function createMockNode(properties) {
+  const node = Object.assign(
+      {
+        htmlAttributes: [],
+        state: {},
+        children: [],
+        unclippedLocation: {left: 20, top: 10, width: 100, height: 50},
+        location: {left: 20, top: 10, width: 100, height: 50},
+      },
+      properties);
+
+  if (node.parent) {
+    // Update children of parent and sibling properties.
+    const parent = node.parent;
+    if (parent.children.length === 0) {
+      parent.children = [];
+      parent.firstChild = node;
+    } else {
+      node.previousSibling = parent.children[parent.children.length - 1];
+      node.previousSibling.nextSibling = node;
+    }
+    parent.children.push(node);
+    parent.lastChild = node;
+  }
+  return node;
+}
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
index 5fbddb8..cabee8ba6 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
@@ -4,11 +4,12 @@
 
 import {InputHandler} from './input_handler.js';
 import {MetricsUtils} from './metrics_utils.js';
+import {NodeNavigationUtils} from './node_navigation_utils.js';
 import {NodeUtils} from './node_utils.js';
 import {ParagraphUtils} from './paragraph_utils.js';
 import {PrefsManager} from './prefs_manager.js';
 import {SelectToSpeakConstants} from './select_to_speak_constants.js';
-import {SentenceUtils} from './sentence_utils.js';
+import {SelectToSpeakUiListener, UiManager} from './ui_manager.js';
 import {WordUtils} from './word_utils.js';
 
 const AutomationNode = chrome.automation.AutomationNode;
@@ -16,10 +17,6 @@
 const EventType = chrome.automation.EventType;
 const RoleType = chrome.automation.RoleType;
 const AccessibilityFeature = chrome.accessibilityPrivate.AccessibilityFeature;
-const SelectToSpeakPanelAction =
-    chrome.accessibilityPrivate.SelectToSpeakPanelAction;
-const FocusRingStackingOrder =
-    chrome.accessibilityPrivate.FocusRingStackingOrder;
 const SelectToSpeakState = chrome.accessibilityPrivate.SelectToSpeakState;
 
 // This must be the same as in ash/system/accessibility/select_to_speak_tray.cc:
@@ -27,33 +24,11 @@
 export const SELECT_TO_SPEAK_TRAY_CLASS_NAME =
     'tray/TrayBackgroundView/SelectToSpeakTray';
 
-// This must match the name of view class that implements the menu view:
-// ash/system/accessibility/select_to_speak_menu_view.h
-const SELECT_TO_SPEAK_MENU_CLASS_NAME = 'SelectToSpeakMenuView';
-
-// This must match the name of view class that implements the speed view:
-// ash/system/accessibility/select_to_speak_speed_view.h
-const SELECT_TO_SPEAK_SPEED_CLASS_NAME = 'SelectToSpeakSpeedView';
-
-// This must match the name of view class that implements the bubble views:
-// ash/system/tray/tray_bubble_view.h
-const TRAY_BUBBLE_VIEW_CLASS_NAME = 'TrayBubbleView';
-
-// This must match the name of view class that implements the buttons used in
-// the floating panel:
-// ash/system/accessibility/floating_menu_button.h
-const FLOATING_MENU_BUTTON_CLASS_NAME = 'FloatingMenuButton';
-
 // Matches one of the known GSuite apps which need the clipboard to find and
 // read selected text. Includes sandbox and non-sandbox versions.
 const GSUITE_APP_REGEXP =
     /^https:\/\/docs\.(?:sandbox\.)?google\.com\/(?:(?:presentation)|(?:document)|(?:spreadsheets)|(?:drawings)){1}\//;
 
-// A RGBA hex string for the default background shading color, which is black at
-// 40% opacity (hex 66). This should be equivalent to using
-// AshColorProvider::ShieldLayerType kShield40.
-const DEFAULT_BACKGROUND_SHADING_COLOR = '#0006';
-
 // Settings key for system speech rate setting.
 const SPEECH_RATE_KEY = 'settings.tts.speech_rate';
 
@@ -77,6 +52,10 @@
   return null;
 }
 
+/**
+ * Select-to-speak component extension controller.
+ * @implements {SelectToSpeakUiListener}
+ */
 export class SelectToSpeak {
   constructor() {
     /**
@@ -107,12 +86,6 @@
     /** @private {chrome.automation.AutomationNode} */
     this.desktop_;
 
-    /**
-     * Button in the floating panel, useful for restoring focus to the panel.
-     * @private {?chrome.automation.AutomationNode}
-     */
-    this.panelButton_ = null;
-
     /** @private {number|undefined} */
     this.intervalRef_;
 
@@ -131,12 +104,6 @@
       desktop.addEventListener(
           EventType.HOVER, this.onHitTestCheckCurrentNodeMatches_.bind(this),
           true);
-
-      // Listen to focus changes so we can grab the floating panel when it
-      // goes into focus, so it can be used later without having to search
-      // through the entire tree.
-      desktop.addEventListener(
-          EventType.FOCUS, this.onFocusChange_.bind(this), true);
     }.bind(this));
 
     /** @private {boolean} */
@@ -207,14 +174,6 @@
      */
     this.supportsNavigationPanel_ = true;
 
-    /**
-     * The position of the current focus ring, which usually highlights the
-     * entire paragraph. Keep this as a member variable so that the control
-     * panel can be updated easily.
-     * @private {!Array<!chrome.accessibilityPrivate.ScreenRect>}
-     */
-    this.currentFocusRing_ = [];
-
     /** @private {boolean} */
     this.visible_ = true;
 
@@ -235,6 +194,9 @@
     this.prefsManager_ = new PrefsManager();
     this.prefsManager_.initPreferences();
 
+    /** @private {!UiManager} */
+    this.uiManager_ = new UiManager(this.prefsManager_, this /* listener */);
+
     this.runContentScripts_();
     this.setUpEventListeners_();
 
@@ -374,31 +336,6 @@
   }
 
   /**
-   * Handles desktop-wide focus changes.
-   * @param {!AutomationEvent} evt
-   * @private
-   */
-  onFocusChange_(evt) {
-    const focusedNode = evt.target;
-
-    // As an optimization, look for the STS floating panel and store in case
-    // we need to access that node at a later point (such as focusing panel).
-    if (focusedNode.className !== FLOATING_MENU_BUTTON_CLASS_NAME) {
-      // When panel is focused, initial focus is always on one of the buttons.
-      return;
-    }
-    const windowParent =
-        AutomationUtil.getFirstAncestorWithRole(focusedNode, RoleType.WINDOW);
-    if (windowParent &&
-        windowParent.className === TRAY_BUBBLE_VIEW_CLASS_NAME &&
-        windowParent.children.length === 1 &&
-        windowParent.children[0].className ===
-            SELECT_TO_SPEAK_MENU_CLASS_NAME) {
-      this.panelButton_ = focusedNode;
-    }
-  }
-
-  /**
    * Queues up selected text for reading by finding the Position objects
    * representing the selection.
    * @private
@@ -626,33 +563,7 @@
       return;
     }
 
-    this.focusPanel_();
-  }
-
-  /**
-   * Sets focus to the floating control panel, if present.
-   * @private
-   */
-  focusPanel_() {
-    // Used cached panel node if possible to avoid expensive desktop.find().
-    // Note: Checking role attribute to see if node is still valid.
-    if (this.panelButton_ && this.panelButton_.role) {
-      // The panel itself isn't focusable, so set focus to most recently
-      // focused panel button.
-      this.panelButton_.focus();
-      return;
-    }
-    this.panelButton_ = null;
-
-    // Fallback to more expensive method of finding panel.
-    const menuView = this.desktop_.find(
-        {attributes: {className: SELECT_TO_SPEAK_MENU_CLASS_NAME}});
-    if (menuView !== null && menuView.parent &&
-        menuView.parent.className === TRAY_BUBBLE_VIEW_CLASS_NAME) {
-      // The menu view's parent is the TrayBubbleView can can be assigned focus.
-      this.panelButton_ = menuView.find({role: RoleType.TOGGLE_BUTTON});
-      this.panelButton_.focus();
-    }
+    this.uiManager_.setFocusToPanel();
   }
 
   /**
@@ -778,7 +689,7 @@
    */
   stopAll_() {
     chrome.tts.stop();
-    this.clearFocusRing_();
+    this.uiManager_.clear();
     this.onStateChanged_(SelectToSpeakState.INACTIVE);
   }
 
@@ -788,7 +699,7 @@
    * @private
    */
   clearFocusRingAndNode_() {
-    this.clearFocusRing_();
+    this.uiManager_.clear();
     // Clear the node and also stop the interval testing.
     this.resetNodes_();
     this.supportsNavigationPanel_ = true;
@@ -812,65 +723,6 @@
   }
 
   /**
-   * Update the navigation floating panel.
-   * @private
-   */
-  updateNavigationPanel_() {
-    if (this.shouldShowNavigationControls_() && this.currentFocusRing_.length) {
-      // If the feature is enabled and we have a valid focus ring, flip the
-      // pause and resume button according to the current STS and TTS state.
-      // Also, update the location of the panel according to the focus ring.
-      chrome.accessibilityPrivate.updateSelectToSpeakPanel(
-          /* show= */ true, /* anchor= */ this.currentFocusRing_[0],
-          /* isPaused= */ this.isPaused_(),
-          /* speed= */ this.speechRateMultiplier_);
-    } else {
-      // Dismiss the panel if either the feature is disabled or the focus ring
-      // is not valid.
-      chrome.accessibilityPrivate.updateSelectToSpeakPanel(/* show= */ false);
-    }
-  }
-
-  /**
-   * Clears the focus ring, but does not clear the current
-   * node.
-   * @private
-   */
-  clearFocusRing_() {
-    this.setFocusRings_([], false /* do not draw background */);
-    chrome.accessibilityPrivate.setHighlights(
-        [], this.prefsManager_.highlightColor());
-    this.updateNavigationPanel_();
-  }
-
-  /**
-   * Sets the focus ring to |rects|. If |drawBackground|, draws the grey focus
-   * background with the alpha set in prefs.
-   * @param {!Array<!chrome.accessibilityPrivate.ScreenRect>} rects
-   * @param {boolean} drawBackground
-   * @private
-   */
-  setFocusRings_(rects, drawBackground) {
-    this.currentFocusRing_ = rects;
-    let color = '#0000';  // Fully transparent.
-    if (drawBackground && this.prefsManager_.backgroundShadingEnabled()) {
-      color = DEFAULT_BACKGROUND_SHADING_COLOR;
-    }
-    // If we're also showing a navigation panel, ensure the focus ring appears
-    // below the panel UI.
-    const stackingOrder = this.shouldShowNavigationControls_() ?
-        FocusRingStackingOrder.BELOW_ACCESSIBILITY_BUBBLES :
-        FocusRingStackingOrder.ABOVE_ACCESSIBILITY_BUBBLES;
-    chrome.accessibilityPrivate.setFocusRings([{
-      rects,
-      type: chrome.accessibilityPrivate.FocusType.GLOW,
-      stackingOrder,
-      color: this.prefsManager_.focusRingColor(),
-      backgroundColor: color,
-    }]);
-  }
-
-  /**
    * Runs content scripts that allow Select-to-Speak access to
    * Google Docs content without a11y mode enabled, in every open
    * tab. Should be run when Select-to-Speak starts up so that any
@@ -934,7 +786,7 @@
       },
       // onSelectionChanged: Mouse selection rect changed.
       onSelectionChanged: rect => {
-        this.setFocusRings_([rect], false /* don't draw background */);
+        this.uiManager_.setSelectionRect(rect);
       },
       // onKeystrokeSelection: Keys pressed for reading highlighted text.
       onKeystrokeSelection: () => {
@@ -950,10 +802,7 @@
       onTextReceived: this.startSpeech_.bind(this)
     });
     this.inputHandler_.setUpEventListeners();
-    chrome.accessibilityPrivate.onSelectToSpeakStateChangeRequested.addListener(
-        this.onStateChangeRequested_.bind(this));
-    chrome.accessibilityPrivate.onSelectToSpeakPanelAction.addListener(
-        this.onSelectToSpeakPanelAction_.bind(this));
+
     chrome.settingsPrivate.onPrefsChanged.addListener(
         this.onPrefsChanged_.bind(this));
     // Initialize the state to SelectToSpeakState.INACTIVE.
@@ -963,7 +812,7 @@
   /**
    * Called when Chrome OS is requesting Select-to-Speak to switch states.
    */
-  onStateChangeRequested_() {
+  onStateChangeRequested() {
     // Switch Select-to-Speak states on request.
     // We will need to track the current state and toggle from one state to
     // the next when this function is called, and then call
@@ -994,56 +843,60 @@
         this.onStateChangeRequestedCallbackForTest_();
   }
 
+  /** Handles user request to navigate to next paragraph. */
+  onNextParagraphRequested() {
+    this.navigateToNextParagraph_(constants.Dir.FORWARD);
+  }
+
+  /** Handles user request to navigate to previous paragraph. */
+  onPreviousParagraphRequested() {
+    this.navigateToNextParagraph_(constants.Dir.BACKWARD);
+  }
+
+  /** Handles user request to navigate to next sentence. */
+  onNextSentenceRequested() {
+    this.navigateToNextSentence_(constants.Dir.FORWARD);
+  }
+
+  /** Handles user request to navigate to previous sentence. */
+  onPreviousSentenceRequested() {
+    this.navigateToNextSentence_(constants.Dir.BACKWARD);
+  }
+
+  /** Handles user request to navigate to exit STS. */
+  onExitRequested() {
+    // User manually requested, so log cancel metric.
+    MetricsUtils.recordCancelIfSpeaking();
+    this.stopAll_();
+  }
+
+  /** Handles user request to pause TTS. */
+  onPauseRequested() {
+    MetricsUtils.recordPauseEvent();
+    this.pause_();
+  }
+
+  /** Handles user request to resume TTS. */
+  onResumeRequested() {
+    if (this.isPaused_()) {
+      MetricsUtils.recordResumeEvent();
+      this.resume_();
+    }
+  }
+
   /**
-   * Handles Select-to-speak panel action.
-   * @param {!SelectToSpeakPanelAction} panelAction Action to perform.
-   * @param {number=} value Optional value associated with action.
+   * Handles user request to adjust reading speed.
+   * @param {number} rateMultiplier
    * @private
    */
-  onSelectToSpeakPanelAction_(panelAction, value) {
-    if (!this.shouldShowNavigationControls_()) {
-      // Ignore if this feature is not enabled.
-      return;
-    }
-    switch (panelAction) {
-      case SelectToSpeakPanelAction.NEXT_PARAGRAPH:
-        this.navigateToNextParagraph_(constants.Dir.FORWARD);
-        break;
-      case SelectToSpeakPanelAction.PREVIOUS_PARAGRAPH:
-        this.navigateToNextParagraph_(constants.Dir.BACKWARD);
-        break;
-      case SelectToSpeakPanelAction.NEXT_SENTENCE:
-        this.navigateToNextSentence_(constants.Dir.FORWARD);
-        break;
-      case SelectToSpeakPanelAction.PREVIOUS_SENTENCE:
-        this.navigateToNextSentence_(
-            constants.Dir.BACKWARD, true /* skipCurrentSentence */);
-        break;
-      case SelectToSpeakPanelAction.EXIT:
-        // User manually requested, so log cancel metric.
-        MetricsUtils.recordCancelIfSpeaking();
-        this.stopAll_();
-        break;
-      case SelectToSpeakPanelAction.PAUSE:
-        MetricsUtils.recordPauseEvent();
-        this.pause_();
-        break;
-      case SelectToSpeakPanelAction.RESUME:
-        if (this.isPaused_()) {
-          MetricsUtils.recordResumeEvent();
-          this.resume_();
-        }
-        break;
-      case SelectToSpeakPanelAction.CHANGE_SPEED:
-        if (!value) {
-          console.warn(
-              'Change speed request receieved with invalid value', value);
-          return;
-        }
-        this.changeSpeed_(value);
-        break;
-      default:
-        // TODO(crbug.com/1140216): Implement other actions.
+  onChangeSpeedRequested(rateMultiplier) {
+    this.speechRateMultiplier_ = rateMultiplier;
+
+    // If currently playing, stop TTS, then resume from current spot.
+    if (!this.isPaused_()) {
+      this.pause_().then(() => {
+        this.resume_();
+      });
     }
   }
 
@@ -1060,222 +913,27 @@
   }
 
   /**
-   * Navigates to the next sentence. First, we search the next sentence in the
-   * current node group. If we do not find one, we will search within the
-   * remaining content in the current paragraph (i.e., text block). If this
-   * still fails, we will search the next paragraph.
-   * TODO(leileilei@google.com): Handle the edge case where the user navigates
-   * to next sentence from the end of a document, see http://crbug.com/1160962.
+   * Navigates to the next sentence.
    * @param {constants.Dir} direction Direction to search for the next sentence.
    *     If set to forward, we look for the sentence start after the current
    *     position. Otherwise, we look for the sentence start before the current
    *     position.
-   * @param {boolean} skipCurrentSentence Whether to skip the current sentence.
-   *     This only affects backward navigation. When set to false, navigating
-   *     backward will find the closest sentence start. When set to true,
-   *     navigating backward will ignore the sentence start in the current
-   *     sentence. For example, when navigating backward from the middle of a
-   *     sentence. A true |skipCurrentSentence| will take us to the start of the
-   *     previous sentence while a false one will take us to the start of the
-   *     current sentence. Regardless of this parameter, navigating backward
-   *     from a sentence start will take us to the start of the previous
-   *     sentence.
    * @private
    */
-  async navigateToNextSentence_(direction, skipCurrentSentence = false) {
-    const currentNodeGroup = this.getCurrentNodeGroup_();
-
-    // An empty node group is not expected and means that the user has not
-    // enqueued any text.
-    if (!currentNodeGroup) {
-      return;
-    }
-
+  async navigateToNextSentence_(direction) {
     if (!this.isPaused_()) {
       await this.pause_();
     }
-
-    // Checks the next sentence within this node group. If we have enqueued the
-    // next sentence that fulfilled the requirements, return.
-    if (this.enqueueNextSentenceWithinNodeGroup_(
-            currentNodeGroup, this.currentCharIndex_, direction,
-            skipCurrentSentence)) {
-      return;
-    }
-
-    // If there is no next sentence at the current node group, look for the
-    // content within this paragraph. First, we get the remaining content in
-    // the paragraph. The returned offset marks the char index of the current
-    // position in the paragraph. When searching forward, the offset is the
-    // char index pointing to the beginning of the remaining content. When
-    // searching backward, the offset is the char index pointing to the char
-    // after the remaining content.
-    const {nodes, offset} = NodeUtils.getNextNodesInParagraphFromNodeGroup(
-        currentNodeGroup, this.currentCharIndex_, direction);
-    // If we have reached to the end of a paragraph, enqueue the sentence from
-    // the next paragraph.
+    const {nodes, offset} = NodeNavigationUtils.getNodesForNextSentence(
+        this.getCurrentNodeGroup_(), this.currentCharIndex_, direction,
+        (nodes) => this.skipPanel_(nodes));
     if (nodes.length === 0) {
-      this.enqueueNextSentenceInNextParagraph_(direction);
-      return;
-    }
-    // Get the node group for the remaining content in the paragraph. If we are
-    // looking for the content after the current position, set startIndex as
-    // offset. Otherwise, set endIndex as offset.
-    const startIndex = direction === constants.Dir.FORWARD ? offset : undefined;
-    const endIndex = direction === constants.Dir.FORWARD ? undefined : offset;
-    const {nodeGroup, startIndexInGroup, endIndexInGroup} =
-        ParagraphUtils.buildSingleNodeGroupWithOffset(
-            nodes, startIndex, endIndex);
-    // Search in the remaining content.
-    const charIndex = direction === constants.Dir.FORWARD ? startIndexInGroup :
-                                                            endIndexInGroup;
-    // The charIndex is guaranteed to be valid at this point, although the
-    // closure compiler is not able to detect it as a valid number.
-    if (charIndex === undefined) {
-      console.warn('Navigate sentence with an invalid char index', charIndex);
-      return;
-    }
-    // When searching backward, we need to adjust |skipCurrentSentence| if it
-    // is true. The remaining content we get excludes the char at
-    // |this.currentCharIndex_|. If this char is a sentence
-    // start, we have already skipped the current sentence so we need to change
-    // |skipCurrentSentence| to false for the next search.
-    if (direction === constants.Dir.BACKWARD && skipCurrentSentence) {
-      const currentPositionIsSentenceStart = SentenceUtils.isSentenceStart(
-          currentNodeGroup, this.currentCharIndex_);
-      if (currentPositionIsSentenceStart) {
-        skipCurrentSentence = false;
-      }
-    }
-    if (this.enqueueNextSentenceWithinNodeGroup_(
-            nodeGroup, charIndex, direction, skipCurrentSentence)) {
-      return;
-    }
-
-    // If there is no next sentence within this paragraph, enqueue the sentence
-    // from the next paragraph.
-    this.enqueueNextSentenceInNextParagraph_(direction);
-  }
-
-  /**
-   * Enqueues the next sentence within the |nodeGroup|. If the |direction|
-   * is set to forward, it will navigate to the sentence start after the
-   * |startCharIndex|. Otherwise, it will look for the sentence start before the
-   * |startCharIndex|.
-   * @param {ParagraphUtils.NodeGroup} nodeGroup
-   * @param {number} startCharIndex The char index that we start from. This
-   *     index is relative to the text content of this node group and is
-   *     exclusive: if a sentence start at 0 and we search with a 0
-   *     |startCharIndex|, this function will return the next sentence start
-   *     after 0 if we search forward.
-   * @param {constants.Dir} direction
-   * @param {boolean} skipCurrentSentence Whether to skip the current sentence
-   *     when navigating backward. See navigateToNextSentence_.
-   * @return {boolean} Whether we have enqueued content to the speech queue.
-   *     When |skipCurrentSentence| is true, we will not enqueue content to
-   *     speech queue if we only find a sentence start in the current sentence.
-   * @private
-   */
-  enqueueNextSentenceWithinNodeGroup_(
-      nodeGroup, startCharIndex, direction, skipCurrentSentence) {
-    if (!nodeGroup) {
-      return false;
-    }
-    let nextSentenceStart =
-        SentenceUtils.getSentenceStart(nodeGroup, startCharIndex, direction);
-    if (nextSentenceStart === null) {
-      return false;
-    }
-    // When we search backward, if we want to skip the current sentence, we
-    // need to search the sentence start in the previous sentence. If the
-    // position of |startCharIndex| is a sentence start, the current
-    // |nextSentenceStart| is already in the previous sentence because
-    // getSentenceStart excludes the search index. Otherwise, the
-    // |nextSentenceStart| we found is the start of current sentence, and we
-    // need to search backward again.
-    if (direction === constants.Dir.BACKWARD && skipCurrentSentence &&
-        !SentenceUtils.isSentenceStart(nodeGroup, startCharIndex)) {
-      nextSentenceStart = SentenceUtils.getSentenceStart(
-          nodeGroup, nextSentenceStart, direction);
-    }
-    // If the second sentence start is not valid, we do not enqueue text,
-    if (nextSentenceStart === null) {
-      return false;
-    }
-
-    // Get the content between the sentence start and the end of the paragraph.
-    const {nodes, offset} = NodeUtils.getNextNodesInParagraphFromNodeGroup(
-        nodeGroup, nextSentenceStart, constants.Dir.FORWARD);
-    if (nodes.length === 0) {
-      // There is no remaining content. Move to the next paragraph. This is
-      // unexpected since we already found a sentence start, which indicates
-      // there should be some content to read.
-      this.enqueueNextSentenceInNextParagraph_(direction);
-    } else {
-      this.startSpeechQueue_(
-          nodes, {clearFocusRing: false, startCharIndex: offset});
-    }
-    return true;
-  }
-
-  /**
-   * Enqueues the next sentence in the next text block in the given
-   * direction. If the |direction| is set to forward, it will navigate to the
-   * start of the following text block. Otherwise, it will look for the last
-   * sentence in the previous text block. This function will enqueue content to
-   * the speech queue regardless of whether we have found a sentence start in
-   * the text block.
-   * @param {constants.Dir} direction
-   * @private
-   */
-  enqueueNextSentenceInNextParagraph_(direction) {
-    const paragraphNodes = this.locateNodesForNextParagraph_(direction);
-    if (paragraphNodes.length === 0) {
       return;
     }
     // Ensure the first node in the paragraph is visible.
-    paragraphNodes[0].makeVisible();
+    nodes[0].makeVisible();
 
-    if (direction === constants.Dir.FORWARD) {
-      // If we are looking for the sentence start in the following text block,
-      // start reading the nodes.
-      this.startSpeechQueue_(paragraphNodes);
-      return;
-    }
-
-    // If we are looking for the previous sentence start, search the last
-    // sentence in the previous text block. Get the node group for the previous
-    // text block. The returned startIndexInGroup and endIndexInGroup are
-    // unused.
-    const {nodeGroup, startIndexInGroup, endIndexInGroup} =
-        ParagraphUtils.buildSingleNodeGroupWithOffset(paragraphNodes);
-    // We search backward for the sentence start before the end of the text
-    // block.
-    const searchOffset = nodeGroup.text.length;
-    const sentenceStartIndex = SentenceUtils.getSentenceStart(
-        nodeGroup, searchOffset, constants.Dir.BACKWARD);
-    // If there is no sentence start in the previous text block, start reading
-    // the block.
-    if (sentenceStartIndex === null) {
-      this.startSpeechQueue_(paragraphNodes);
-      return;
-    }
-    // Gets the remaining content between the sentence start until the end of
-    // the text block. The offset is the start char index for the first node in
-    // the remaining content.
-    const {nodes, offset} = NodeUtils.getNextNodesInParagraphFromNodeGroup(
-        nodeGroup, sentenceStartIndex, constants.Dir.FORWARD);
-    if (nodes.length === 0) {
-      // If there is no remaining content, start reading the block. This is
-      // unexpected since we already found a sentence start, which indicates
-      // there should be some content to read.
-      this.startSpeechQueue_(paragraphNodes);
-      return;
-    }
-    // Reads the remaining content from the sentence start until the end of the
-    // block.
-    this.startSpeechQueue_(
-        nodes, {clearFocusRing: false, startCharIndex: offset});
+    this.startSpeechQueue_(nodes, {startCharIndex: offset});
   }
 
   /**
@@ -1289,10 +947,14 @@
       await this.pause_();
     }
 
-    const nodes = this.locateNodesForNextParagraph_(direction);
+    const nodes = NodeNavigationUtils.getNodesForNextParagraph(
+        this.getCurrentNodeGroup_(), direction,
+        (nodes) => this.skipPanel_(nodes));
+    // Return early if the nodes are empty.
     if (nodes.length === 0) {
       return;
     }
+
     // Ensure the first node in the paragraph is visible.
     nodes[0].makeVisible();
 
@@ -1300,59 +962,15 @@
   }
 
   /**
-   * Finds the nodes for the next text block in the given direction. This
-   * function is based on |NodeUtils.getNextParagraph| but provides additional
-   * checks on the anchor node used for searchiong.
-   * @param {constants.Dir} direction
-   * @return {Array<!AutomationNode>} A list of nodes for the next block in the
-   *     given direction.
+   * A predicate for paragraph selection and navigation. The current
+   * implementation filters out paragraph that belongs to the panel.
+   * @param {Array<!AutomationNode>} nodes
+   * @return {boolean} Whether the paragraph made of the |nodes| is valid
    * @private
    */
-  locateNodesForNextParagraph_(direction) {
-    // Use current block parent as starting point to navigate from. If it is not
-    // a valid block, then use one of the nodes that are currently activated.
-    const currentNodeGroup = this.getCurrentNodeGroup_();
-    if (!currentNodeGroup) {
-      return [];
-    }
-    let node = currentNodeGroup.blockParent;
-    if ((node === null || node.isRootNode || node.role === undefined) &&
-        currentNodeGroup.nodes.length > 0) {
-      node = currentNodeGroup.nodes[0].node;
-    }
-    if (node === null || node.role === undefined) {
-      // Could not find any nodes to navigate from.
-      return [];
-    }
-
-    // Retrieve the nodes that make up the next/prev paragraph.
-    const nextParagraphNodes = NodeUtils.getNextParagraph(node, direction);
-    if (nextParagraphNodes.length === 0) {
-      // Cannot find any valid nodes in given direction.
-      return [];
-    }
-    if (AutomationUtil.getAncestors(nextParagraphNodes[0])
-            .find((n) => this.isPanel_(n))) {
-      // Do not navigate to Select-to-speak panel.
-      return [];
-    }
-
-    return nextParagraphNodes;
-  }
-
-  /**
-   * Updates current reading speed given a multiplier.
-   * @param {number} rateMultiplier
-   * @private
-   */
-  async changeSpeed_(rateMultiplier) {
-    this.speechRateMultiplier_ = rateMultiplier;
-
-    // If currently playing, stop TTS, then resume from current spot.
-    if (!this.isPaused_()) {
-      await this.pause_();
-      this.resume_();
-    }
+  skipPanel_(nodes) {
+    return !AutomationUtil.getAncestors(nodes[0]).find(
+        (n) => UiManager.isPanel(n));
   }
 
   /**
@@ -1712,6 +1330,8 @@
     if (this.intervalRef_ !== undefined) {
       clearInterval(this.intervalRef_);
     }
+
+    // TODO(crbug.com/1179812): Move polling into UiManager.
     this.intervalRef_ = setInterval(
         this.testCurrentNode_.bind(this),
         SelectToSpeakConstants.NODE_STATE_TEST_INTERVAL_MS);
@@ -1768,9 +1388,6 @@
       }
     } else {
       this.currentNodeWord_ = null;
-      // There are many cases where we won't update the node highlight or test
-      // the node. Thus, we need to update the panel independently.
-      this.updateNavigationPanel_();
     }
   }
 
@@ -1842,7 +1459,7 @@
         // If the node is invalid, continue speech unless readAfterClose_
         // is set to true. See https://crbug.com/818835 for more.
         if (this.readAfterClose_) {
-          this.clearFocusRing_();
+          this.uiManager_.clear();
           this.visible_ = false;
         } else {
           this.stopAll_();
@@ -1851,8 +1468,8 @@
       case NodeUtils.NodeState.NODE_STATE_INVISIBLE:
         // If it is invisible but still valid, just clear the focus ring.
         // Don't clear the current node because we may still use it
-        // if it becomes visibile later.
-        this.clearFocusRing_();
+        // if it becomes visible later.
+        this.uiManager_.clear();
         this.visible_ = false;
         break;
       case NodeUtils.NodeState.NODE_STATE_NORMAL:
@@ -1862,7 +1479,7 @@
           // Just came to the foreground.
           this.updateHighlightAndFocus_(nodeGroupItem);
         } else if (!inForeground) {
-          this.clearFocusRing_();
+          this.uiManager_.clear();
           this.visible_ = false;
         }
     }
@@ -1906,6 +1523,7 @@
       if (node.role === RoleType.INLINE_TEXT_BOX) {
         charIndexInParent = ParagraphUtils.getStartCharIndexInParent(node);
       }
+      // TODO(crbug.com/1179812): Move highlighting to UiManager.
       node.boundsForRange(
           this.currentNodeWord_.start - charIndexInParent,
           this.currentNodeWord_.end - charIndexInParent, (bounds) => {
@@ -1918,25 +1536,16 @@
             }
           });
     }
-    // Show the parent element of the currently verbalized node with the
-    // focus ring. This is a nicer user-facing behavior than jumping from
-    // node to node, as nodes may not correspond well to paragraphs or
-    // blocks.
-    // TODO: Better test: has no siblings in the group, highlight just
-    // the one node. if it has siblings, highlight the parent.
-    let focusRingRect;
+
     const currentNodeGroup = this.getCurrentNodeGroup_();
-    if (!currentNodeGroup) {
-      return;
+    if (currentNodeGroup) {
+      this.uiManager_.update(
+          currentNodeGroup, /** @type {!AutomationNode} */ (node), {
+            showPanel: this.shouldShowNavigationControls_(),
+            paused: this.isPaused_(),
+            speechRateMultiplier: this.speechRateMultiplier_,
+          });
     }
-    const currentBlockParent = currentNodeGroup.blockParent;
-    if (currentBlockParent !== null && node.role === RoleType.INLINE_TEXT_BOX) {
-      focusRingRect = currentBlockParent.location;
-    } else {
-      focusRingRect = node.location;
-    }
-    this.setFocusRings_([focusRingRect], true /* draw background */);
-    this.updateNavigationPanel_();
   }
 
   /**
@@ -1944,6 +1553,8 @@
    * @private
    */
   testCurrentNode_() {
+    // TODO(crbug.com/1179812): Consider moving to UiManager.
+
     if (this.currentNodeGroupItem_ == null) {
       return;
     }
@@ -1979,8 +1590,9 @@
       var inForeground =
           currentWindow != null && window != null && currentWindow === window;
       if (!inForeground &&
-          (this.isPanel_(window) ||
-           this.isPanel_(NodeUtils.getNearestContainingWindow(focusedNode)))) {
+          (UiManager.isPanel(window) ||
+           UiManager.isPanel(
+               NodeUtils.getNearestContainingWindow(focusedNode)))) {
         // If the focus is on the Select-to-speak panel or the hit test landed
         // on the panel, treat the current node as if it is in the foreground.
         inForeground = true;
@@ -1999,24 +1611,7 @@
     }.bind(this));
   }
 
-  /**
-   * @param {?AutomationNode|undefined} node
-   * @return {boolean} Whether given node is the Select-to-speak floating panel.
-   * @private
-   */
-  isPanel_(node) {
-    if (!node) {
-      return false;
-    }
 
-    // Determine if the node is part of the floating panel or the reading speed
-    // selection bubble.
-    return (
-        node.className === TRAY_BUBBLE_VIEW_CLASS_NAME &&
-        node.children.length === 1 &&
-        (node.children[0].className === SELECT_TO_SPEAK_MENU_CLASS_NAME ||
-         node.children[0].className === SELECT_TO_SPEAK_SPEED_CLASS_NAME));
-  }
 
   /**
    * Updates the currently highlighted node word based on the current text
@@ -2093,8 +1688,8 @@
     }
     // Do not show panel on system UI. System UI can be problematic due to
     // auto-dismissing behavior (see http://crbug.com/1157148), but also
-    // navigation controls do not work well control-rich interfaces that are
-    // light on text (and therefore sentence and paragraph structures).
+    // navigation controls do not work well for control-rich interfaces that are
+    // light on text (and therefore no sentence and paragraph structures).
     return !nodes.some((n) => n.root && n.root.role === RoleType.DESKTOP);
   }
 
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 75f0abe2..97afc42a 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
@@ -106,9 +106,7 @@
             // TODO(joelriley@google.com): Figure out a better way to trigger
             // the actual floating panel button rather than calling private
             // method directly.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                    .NEXT_PARAGRAPH);
+            selectToSpeak.onNextParagraphRequested();
 
             // Speaks second paragraph
             this.waitOneEventLoop(() => {
@@ -140,9 +138,7 @@
             // TODO(joelriley@google.com): Figure out a better way to trigger
             // the actual floating panel button rather than calling private
             // method directly.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                    .PREVIOUS_PARAGRAPH);
+            selectToSpeak.onPreviousParagraphRequested();
 
             // Speaks second paragraph
             this.waitOneEventLoop(() => {
@@ -215,15 +211,13 @@
                 'First sentence. Second sentence. Third sentence.');
 
             // Hitting pause will stop the current TTS.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
+            selectToSpeak.onPauseRequested();
             assertFalse(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 0);
 
             // Hitting resume will start from the remaining content of the
             // second sentence.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
+            selectToSpeak.onResumeRequested();
             assertTrue(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 1);
             this.assertEqualsCollapseWhitespace(
@@ -251,15 +245,13 @@
                 'First sentence. Second sentence. Third sentence.');
 
             // Hitting pause will stop the current TTS.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
+            selectToSpeak.onPauseRequested();
             assertFalse(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 0);
 
             // Hitting resume will start from the beginning of the third
             // sentence.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
+            selectToSpeak.onResumeRequested();
             assertTrue(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 1);
             this.assertEqualsCollapseWhitespace(
@@ -285,15 +277,13 @@
                 this.mockTts.pendingUtterances()[0], 'first sentence.');
 
             // Hitting pause will stop the current TTS.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
+            selectToSpeak.onPauseRequested();
             assertFalse(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 0);
 
             // Hitting resume will start from the remaining content of the
             // paragraph.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
+            selectToSpeak.onResumeRequested();
             assertTrue(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 1);
             this.assertEqualsCollapseWhitespace(
@@ -323,15 +313,13 @@
                 this.mockTts.pendingUtterances()[0], 'Paragraph one.');
 
             // Hitting pause will stop the current TTS.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
+            selectToSpeak.onPauseRequested();
             assertFalse(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 0);
 
             // Hitting resume will start from the remaining content of the
             // paragraph.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
+            selectToSpeak.onResumeRequested();
             assertTrue(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 1);
             this.assertEqualsCollapseWhitespace(
@@ -367,9 +355,7 @@
             this.triggerReadSelectedText();
 
             // Navigates to the next paragraph and speaks until the second word.
-            await selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                    .NEXT_PARAGRAPH);
+            await selectToSpeak.onNextParagraphRequested();
             this.mockTts.speakUntilCharIndex(10);
             assertTrue(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 1);
@@ -378,12 +364,10 @@
 
             // Hitting pause and resume will start reading the remaining content
             // in the second paragraph.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
+            selectToSpeak.onPauseRequested();
             assertFalse(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 0);
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
+            selectToSpeak.onResumeRequested();
             assertTrue(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 1);
             this.assertEqualsCollapseWhitespace(
@@ -411,9 +395,7 @@
             this.triggerReadSelectedText();
             // Navigates to the next sentence and speaks until the last word
             // (i.e., "two") in the first pargraph.
-            await selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                    .NEXT_SENTENCE);
+            await selectToSpeak.onNextSentenceRequested();
             this.mockTts.speakUntilCharIndex(23);
             assertTrue(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 1);
@@ -422,12 +404,10 @@
 
             // Hitting pause and resume will start reading the remaining content
             // in the first paragraph.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
+            selectToSpeak.onPauseRequested();
             assertFalse(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 0);
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
+            selectToSpeak.onResumeRequested();
             assertTrue(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 1);
             this.assertEqualsCollapseWhitespace(
@@ -459,15 +439,13 @@
                 'Sentence one . Sentence two.');
 
             // Hitting pause will stop the current TTS.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
+            selectToSpeak.onPauseRequested();
             assertFalse(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 0);
 
             // Hitting resume will start from the remaining content of the
             // paragraph.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
+            selectToSpeak.onResumeRequested();
             assertTrue(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 1);
             this.assertEqualsCollapseWhitespace(
@@ -505,15 +483,13 @@
                   this.mockTts.pendingUtterances()[0], 'is some bold text');
 
               // Hitting pause will stop the current TTS.
-              selectToSpeak.onSelectToSpeakPanelAction_(
-                  chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
+              selectToSpeak.onPauseRequested();
               assertFalse(this.mockTts.currentlySpeaking());
               assertEquals(this.mockTts.pendingUtterances().length, 0);
 
               // Hitting resume will start from the remaining content of the
               // paragraph.
-              selectToSpeak.onSelectToSpeakPanelAction_(
-                  chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
+              selectToSpeak.onResumeRequested();
               assertTrue(this.mockTts.currentlySpeaking());
               assertEquals(this.mockTts.pendingUtterances().length, 1);
               this.assertEqualsCollapseWhitespace(
@@ -548,8 +524,7 @@
             'This is the first. This is the second.');
 
         // Hitting next sentence will start another TTS.
-        await selectToSpeak.onSelectToSpeakPanelAction_(
-            chrome.accessibilityPrivate.SelectToSpeakPanelAction.NEXT_SENTENCE);
+        await selectToSpeak.onNextSentenceRequested();
         assertTrue(this.mockTts.currentlySpeaking());
         assertEquals(this.mockTts.pendingUtterances().length, 1);
         this.assertEqualsCollapseWhitespace(
@@ -575,9 +550,7 @@
                 this.mockTts.pendingUtterances()[0], 'Sent 2.');
 
             // Hitting next sentence will start from the next sentence.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                    .NEXT_SENTENCE);
+            selectToSpeak.onNextSentenceRequested();
             this.waitOneEventLoop(() => {
               assertTrue(this.mockTts.currentlySpeaking());
               assertEquals(this.mockTts.pendingUtterances().length, 1);
@@ -607,9 +580,7 @@
 
             // Hitting next sentence will star from the next paragraph as there
             // is no more sentence in the current paragraph.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                    .NEXT_SENTENCE);
+            selectToSpeak.onNextSentenceRequested();
             this.waitOneEventLoop(() => {
               assertTrue(this.mockTts.currentlySpeaking());
               assertEquals(this.mockTts.pendingUtterances().length, 1);
@@ -636,9 +607,7 @@
             'First sentence. Second sentence. Third sentence.');
 
         // Hitting prev sentence will start another TTS.
-        await selectToSpeak.onSelectToSpeakPanelAction_(
-            chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                .PREVIOUS_SENTENCE);
+        await selectToSpeak.onPreviousSentenceRequested();
         assertTrue(this.mockTts.currentlySpeaking());
         assertEquals(this.mockTts.pendingUtterances().length, 1);
         this.assertEqualsCollapseWhitespace(
@@ -667,9 +636,7 @@
                 'First sentence. Second sentence. Third sentence.');
 
             // Hitting prev sentence will start another TTS.
-            await selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                    .PREVIOUS_SENTENCE);
+            await selectToSpeak.onPreviousSentenceRequested();
             assertTrue(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 1);
             this.assertEqualsCollapseWhitespace(
@@ -695,9 +662,7 @@
                 this.mockTts.pendingUtterances()[0], 'Sent 2.');
 
             // Hitting previous sentence will start from the previous sentence.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                    .PREVIOUS_SENTENCE);
+            selectToSpeak.onPreviousSentenceRequested();
             this.waitOneEventLoop(() => {
               assertTrue(this.mockTts.currentlySpeaking());
               assertEquals(this.mockTts.pendingUtterances().length, 1);
@@ -728,9 +693,7 @@
             // Hitting previous sentence will start from the last sentence in
             // the previous paragraph as there is no more sentence in the
             // current paragraph.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                    .PREVIOUS_SENTENCE);
+            selectToSpeak.onPreviousSentenceRequested();
             this.waitOneEventLoop(() => {
               assertTrue(this.mockTts.currentlySpeaking());
               assertEquals(this.mockTts.pendingUtterances().length, 1);
@@ -761,10 +724,7 @@
 
             // Changing speed will resume with the remaining content of the
             // current sentence.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                    .CHANGE_SPEED,
-                1.5);
+            selectToSpeak.onChangeSpeedRequested(1.5);
             assertFalse(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 0);
 
@@ -794,11 +754,8 @@
         this.triggerReadSelectedText();
 
         // Changing speed then exit.
-        selectToSpeak.onSelectToSpeakPanelAction_(
-            chrome.accessibilityPrivate.SelectToSpeakPanelAction.CHANGE_SPEED,
-            1.5);
-        selectToSpeak.onSelectToSpeakPanelAction_(
-            chrome.accessibilityPrivate.SelectToSpeakPanelAction.EXIT);
+        selectToSpeak.onChangeSpeedRequested(1.5);
+        selectToSpeak.onExitRequested();
         assertFalse(this.mockTts.currentlySpeaking());
         assertEquals(this.mockTts.pendingUtterances().length, 0);
 
@@ -828,16 +785,12 @@
             assertEquals(this.mockTts.getOptions().rate, 1.2);
 
             // User-intiated pause.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.PAUSE);
+            selectToSpeak.onPauseRequested();
             assertFalse(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 0);
 
             // Changing speed will remain paused.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                    .CHANGE_SPEED,
-                1.5);
+            selectToSpeak.onChangeSpeedRequested(1.5);
 
             // Wait an event loop so all pending promises are resolved prior to
             // asserting that TTS remains paused.
@@ -863,8 +816,7 @@
             this.mockTts.finishPendingUtterance();
 
             // Hitting resume will start the next paragraph.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
+            selectToSpeak.onResumeRequested();
             assertTrue(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 1);
             this.assertEqualsCollapseWhitespace(
@@ -887,8 +839,7 @@
             this.mockTts.finishPendingUtterance();
 
             // Hitting resume will start the remaining content.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
+            selectToSpeak.onResumeRequested();
             assertTrue(this.mockTts.currentlySpeaking());
             assertEquals(this.mockTts.pendingUtterances().length, 1);
             this.assertEqualsCollapseWhitespace(
@@ -925,8 +876,7 @@
 
               // Hitting resume will start from the remaining content of the
               // paragraph.
-              selectToSpeak.onSelectToSpeakPanelAction_(
-                  chrome.accessibilityPrivate.SelectToSpeakPanelAction.RESUME);
+              selectToSpeak.onResumeRequested();
               assertTrue(this.mockTts.currentlySpeaking());
               assertEquals(this.mockTts.pendingUtterances().length, 1);
               this.assertEqualsCollapseWhitespace(
@@ -1036,9 +986,7 @@
             const speakOptions = this.mockTts.getOptions();
 
             // Navigate to next paragraph before speech begins.
-            selectToSpeak.onSelectToSpeakPanelAction_(
-                chrome.accessibilityPrivate.SelectToSpeakPanelAction
-                    .NEXT_PARAGRAPH);
+            selectToSpeak.onNextParagraphRequested();
 
             this.waitOneEventLoop(() => {
               // Manually triggered delayed events.
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.js
new file mode 100644
index 0000000..4352770c
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.js
@@ -0,0 +1,352 @@
+// 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 {ParagraphUtils} from './paragraph_utils.js';
+import {PrefsManager} from './prefs_manager.js';
+
+const AutomationEvent = chrome.automation.AutomationEvent;
+const AutomationNode = chrome.automation.AutomationNode;
+const EventType = chrome.automation.EventType;
+const FocusRingStackingOrder =
+    chrome.accessibilityPrivate.FocusRingStackingOrder;
+const RoleType = chrome.automation.RoleType;
+const SelectToSpeakPanelAction =
+    chrome.accessibilityPrivate.SelectToSpeakPanelAction;
+
+// This must match the name of view class that implements the menu view:
+// ash/system/accessibility/select_to_speak_menu_view.h
+const SELECT_TO_SPEAK_MENU_CLASS_NAME = 'SelectToSpeakMenuView';
+
+// This must match the name of view class that implements the speed view:
+// ash/system/accessibility/select_to_speak_speed_view.h
+const SELECT_TO_SPEAK_SPEED_CLASS_NAME = 'SelectToSpeakSpeedView';
+
+// This must match the name of view class that implements the bubble views:
+// ash/system/tray/tray_bubble_view.h
+const TRAY_BUBBLE_VIEW_CLASS_NAME = 'TrayBubbleView';
+
+// This must match the name of view class that implements the buttons used in
+// the floating panel:
+// ash/system/accessibility/floating_menu_button.h
+const FLOATING_MENU_BUTTON_CLASS_NAME = 'FloatingMenuButton';
+
+// A RGBA hex string for the default background shading color, which is black at
+// 40% opacity (hex 66). This should be equivalent to using
+// AshColorProvider::ShieldLayerType kShield40.
+const DEFAULT_BACKGROUND_SHADING_COLOR = '#0006';
+
+/**
+ * Callbacks invoked when users perform actions in the UI.
+ * @interface
+ */
+export class SelectToSpeakUiListener {
+  constructor() {}
+
+  /** User requests navigation to next paragraph. */
+  onNextParagraphRequested() {}
+
+  /** User requests navigation to previous paragraph. */
+  onPreviousParagraphRequested() {}
+
+  /** User requests navigation to next sentence. */
+  onNextSentenceRequested() {}
+
+  /** User requests navigation to previous sentence. */
+  onPreviousSentenceRequested() {}
+
+  /** User requests pausing TTS. */
+  onPauseRequested() {}
+
+  /** User requests resuming TTS. */
+  onResumeRequested() {}
+
+  /**
+   * User requests reading speed adjustment.
+   * @param {number} speed Speech rate multiplier.
+   */
+  onChangeSpeedRequested(speed) {}
+
+  /** User requests exiting STS. */
+  onExitRequested() {}
+
+  /** User requests state change via tray button. */
+  onStateChangeRequested() {}
+}
+
+/**
+ * Manages user interface elements controlled by Select-to-speak, such the
+ * focus ring, floating control panel, and tray button.
+ *
+ * TODO(crbug.com/1179812): Also move word highlighting here.
+ */
+export class UiManager {
+  /**
+   * @param {!PrefsManager} prefsManager
+   * @param {!SelectToSpeakUiListener} listener
+   */
+  constructor(prefsManager, listener) {
+    /** @private {!PrefsManager} */
+    this.prefsManager_ = prefsManager;
+
+    /** @private {!SelectToSpeakUiListener} */
+    this.listener_ = listener;
+
+    /** @private {?chrome.automation.AutomationNode} */
+    this.desktop_ = null;
+
+    /**
+     * Button in the floating panel, useful for restoring focus to the panel.
+     * @private {?chrome.automation.AutomationNode}
+     */
+    this.panelButton_ = null;
+
+    // Cache desktop and listen to focus changes.
+    chrome.automation.getDesktop((desktop) => {
+      this.desktop_ = desktop;
+
+      // Listen to focus changes so we can grab the floating panel when it
+      // goes into focus, so it can be used later without having to search
+      // through the entire tree.
+      desktop.addEventListener(EventType.FOCUS, (evt) => {
+        this.onFocusChange_(evt);
+      }, true);
+    });
+
+    // Listen to panel events.
+    chrome.accessibilityPrivate.onSelectToSpeakPanelAction.addListener(
+        (panelAction, value) => {
+          this.onPanelAction_(panelAction, value);
+        });
+
+    // Listen to event from activating tray button.
+    chrome.accessibilityPrivate.onSelectToSpeakStateChangeRequested.addListener(
+        () => {
+          this.listener_.onStateChangeRequested();
+        });
+  }
+
+  /**
+   * Handles Select-to-speak panel action.
+   * @param {!SelectToSpeakPanelAction} panelAction Action to perform.
+   * @param {number=} value Optional value associated with action.
+   * @private
+   */
+  onPanelAction_(panelAction, value) {
+    switch (panelAction) {
+      case SelectToSpeakPanelAction.NEXT_PARAGRAPH:
+        this.listener_.onNextParagraphRequested();
+        break;
+      case SelectToSpeakPanelAction.PREVIOUS_PARAGRAPH:
+        this.listener_.onPreviousParagraphRequested();
+        break;
+      case SelectToSpeakPanelAction.NEXT_SENTENCE:
+        this.listener_.onNextSentenceRequested();
+        break;
+      case SelectToSpeakPanelAction.PREVIOUS_SENTENCE:
+        this.listener_.onPreviousSentenceRequested();
+        break;
+      case SelectToSpeakPanelAction.EXIT:
+        this.listener_.onExitRequested();
+        break;
+      case SelectToSpeakPanelAction.PAUSE:
+        this.listener_.onPauseRequested();
+        break;
+      case SelectToSpeakPanelAction.RESUME:
+        this.listener_.onResumeRequested();
+        break;
+      case SelectToSpeakPanelAction.CHANGE_SPEED:
+        if (!value) {
+          console.warn(
+              'Change speed request receieved with invalid value', value);
+          return;
+        }
+        this.listener_.onChangeSpeedRequested(value);
+        break;
+      default:
+        console.warn('Unknown panel action received', panelAction);
+    }
+  }
+
+  /**
+   * Handles desktop-wide focus changes.
+   * @param {!AutomationEvent} evt
+   * @private
+   */
+  onFocusChange_(evt) {
+    const focusedNode = evt.target;
+
+    // As an optimization, look for the STS floating panel and store in case
+    // we need to access that node at a later point (such as focusing panel).
+    if (focusedNode.className !== FLOATING_MENU_BUTTON_CLASS_NAME) {
+      // When panel is focused, initial focus is always on one of the buttons.
+      return;
+    }
+    const windowParent =
+        AutomationUtil.getFirstAncestorWithRole(focusedNode, RoleType.WINDOW);
+    if (windowParent &&
+        windowParent.className === TRAY_BUBBLE_VIEW_CLASS_NAME &&
+        windowParent.children.length === 1 &&
+        windowParent.children[0].className ===
+            SELECT_TO_SPEAK_MENU_CLASS_NAME) {
+      this.panelButton_ = focusedNode;
+    }
+  }
+
+  /**
+   * Sets focus to the floating control panel, if present.
+   */
+  setFocusToPanel() {
+    // Used cached panel node if possible to avoid expensive desktop.find().
+    // Note: Checking role attribute to see if node is still valid.
+    if (this.panelButton_ && this.panelButton_.role) {
+      // The panel itself isn't focusable, so set focus to most recently
+      // focused panel button.
+      this.panelButton_.focus();
+      return;
+    }
+    this.panelButton_ = null;
+
+    // Fallback to more expensive method of finding panel.
+    if (!this.desktop_) {
+      console.error('No cached desktop object, cannot focus panel');
+      return;
+    }
+    const menuView = this.desktop_.find(
+        {attributes: {className: SELECT_TO_SPEAK_MENU_CLASS_NAME}});
+    if (menuView !== null && menuView.parent &&
+        menuView.parent.className === TRAY_BUBBLE_VIEW_CLASS_NAME) {
+      // The menu view's parent is the TrayBubbleView can can be assigned focus.
+      this.panelButton_ = menuView.find({role: RoleType.TOGGLE_BUTTON});
+      this.panelButton_.focus();
+    }
+  }
+
+  /**
+   * Sets the focus ring to |rects|. If |drawBackground|, draws the grey focus
+   * background with the alpha set in prefs. |panelVisible| determines
+   * the stacking order, so focus rings do not appear on top of panel.
+   * @param {!Array<!chrome.accessibilityPrivate.ScreenRect>} rects
+   * @param {boolean} drawBackground
+   * @param {boolean} panelVisible
+   * @private
+   */
+  setFocusRings_(rects, drawBackground, panelVisible) {
+    let color = '#0000';  // Fully transparent.
+    if (drawBackground && this.prefsManager_.backgroundShadingEnabled()) {
+      color = DEFAULT_BACKGROUND_SHADING_COLOR;
+    }
+    // If we're also showing a floating panel, ensure the focus ring appears
+    // below the panel UI.
+    const stackingOrder = panelVisible ?
+        FocusRingStackingOrder.BELOW_ACCESSIBILITY_BUBBLES :
+        FocusRingStackingOrder.ABOVE_ACCESSIBILITY_BUBBLES;
+    chrome.accessibilityPrivate.setFocusRings([{
+      rects,
+      type: chrome.accessibilityPrivate.FocusType.GLOW,
+      stackingOrder,
+      color: this.prefsManager_.focusRingColor(),
+      backgroundColor: color,
+    }]);
+  }
+
+  /**
+   * Updates the floating control panel.
+   * @param {boolean} showPanel
+   * @param {!chrome.accessibilityPrivate.ScreenRect=} anchorRect
+   * @param {boolean=} paused
+   * @param {number=} speechRateMultiplier
+   * @private
+   */
+  updatePanel_(showPanel, anchorRect, paused, speechRateMultiplier) {
+    if (showPanel) {
+      if (anchorRect === undefined || paused === undefined ||
+          speechRateMultiplier === undefined) {
+        console.error('Cannot display panel: missing required parameters');
+        return;
+      }
+      // If the feature is enabled and we have a valid focus ring, flip the
+      // pause and resume button according to the current STS and TTS state.
+      // Also, update the location of the panel according to the focus ring.
+      chrome.accessibilityPrivate.updateSelectToSpeakPanel(
+          /* show= */ true, /* anchor= */ anchorRect,
+          /* isPaused= */ paused,
+          /* speed= */ speechRateMultiplier);
+    } else {
+      // Dismiss the panel if either the feature is disabled or the focus ring
+      // is not valid.
+      chrome.accessibilityPrivate.updateSelectToSpeakPanel(/* show= */ false);
+    }
+  }
+
+  /**
+   * Renders user selection rect, in the form of a focus ring.
+   * @param {!chrome.accessibilityPrivate.ScreenRect} rect
+   */
+  setSelectionRect(rect) {
+    // TODO(crbug.com/1185238): Support showing two focus rings at once, in case
+    // a focus ring highlighting a node group is already present.
+    this.setFocusRings_(
+        [rect], false /* don't draw background */, false /* panelVisible */);
+  }
+
+  /**
+   * Updates overlay UI based on current node and panel state.
+   * @param {!ParagraphUtils.NodeGroup} nodeGroup Current node group.
+   * @param {!AutomationNode} node Current node being spoken.
+   * @param {!{showPanel: boolean,
+   *          paused: boolean,
+   *          speechRateMultiplier: number}} panelState
+   */
+  update(nodeGroup, node, panelState) {
+    const {showPanel, paused, speechRateMultiplier} = panelState;
+    // Show the parent element of the currently verbalized node with the
+    // focus ring. This is a nicer user-facing behavior than jumping from
+    // node to node, as nodes may not correspond well to paragraphs or
+    // blocks.
+    // TODO: Better test: has no siblings in the group, highlight just
+    // the one node. if it has siblings, highlight the parent.
+    let focusRingRect;
+    const currentBlockParent = nodeGroup.blockParent;
+    if (currentBlockParent !== null && node.role === RoleType.INLINE_TEXT_BOX) {
+      focusRingRect = currentBlockParent.location;
+    } else {
+      focusRingRect = node.location;
+    }
+    if (!focusRingRect) {
+      console.warn('Could not determine node location; cannot render UI');
+      return;
+    }
+    this.setFocusRings_([focusRingRect], true /* draw background */, showPanel);
+    this.updatePanel_(showPanel, focusRingRect, paused, speechRateMultiplier);
+  }
+
+  /**
+   * Clears overlay UI, hiding focus rings, panel, and word highlight.
+   */
+  clear() {
+    this.setFocusRings_(
+        [], false /* do not draw background */, false /* panel not visible */);
+    chrome.accessibilityPrivate.setHighlights(
+        [], this.prefsManager_.highlightColor());
+    this.updatePanel_(false /* hide panel */);
+  }
+
+  /**
+   * @param {?AutomationNode|undefined} node
+   * @return {boolean} Whether given node is the Select-to-speak floating panel.
+   */
+  static isPanel(node) {
+    if (!node) {
+      return false;
+    }
+
+    // Determine if the node is part of the floating panel or the reading speed
+    // selection bubble.
+    return (
+        node.className === TRAY_BUBBLE_VIEW_CLASS_NAME &&
+        node.children.length === 1 &&
+        (node.children[0].className === SELECT_TO_SPEAK_MENU_CLASS_NAME ||
+         node.children[0].className === SELECT_TO_SPEAK_SPEED_CLASS_NAME));
+  }
+}
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager_unittest.js
new file mode 100644
index 0000000..6d4f36c
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager_unittest.js
@@ -0,0 +1,246 @@
+// 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.
+
+GEN_INCLUDE(['select_to_speak_e2e_test_base.js']);
+GEN_INCLUDE(['../common/testing/mock_accessibility_private.js']);
+
+/** Focus ring color to use in testing. */
+const FOCUS_RING_COLOR = '#ff0';
+
+/** Highlight color to use in testing. */
+const HIGHLIGHT_COLOR = '#00f';
+
+/** Mock SelectToSpeakUiListener. */
+class MockUiListener {
+  constructor() {
+    this.onNextParagraphRequestedCalled = false;
+    this.onPreviousParagraphRequestedCalled = false;
+    this.onNextSentenceRequestedCalled = false;
+    this.onPreviousSentenceRequestedCalled = false;
+    this.onPauseRequestedCalled = false;
+    this.onResumeRequestedCalled = false;
+    this.onChangeSpeedRequestedValue = undefined;
+    this.onExitRequestedCalled = false;
+    this.onStateChangeRequestedCalled = false;
+  }
+
+  onNextParagraphRequested() {
+    this.onNextParagraphRequestedCalled = true;
+  }
+
+  onPreviousParagraphRequested() {
+    this.onPreviousParagraphRequestedCalled = true;
+  }
+
+  onNextSentenceRequested() {
+    this.onNextSentenceRequestedCalled = true;
+  }
+
+  onPreviousSentenceRequested() {
+    this.onPreviousSentenceRequestedCalled = true;
+  }
+
+  onPauseRequested() {
+    this.onPauseRequestedCalled = true;
+  }
+
+  onResumeRequested() {
+    this.onResumeRequestedCalled = true;
+  }
+
+  onChangeSpeedRequested(speed) {
+    this.onChangeSpeedRequestedValue = speed;
+  }
+
+  onExitRequested() {
+    this.onExitRequestedCalled = true;
+  }
+
+  onStateChangeRequested() {
+    this.onStateChangeRequestedCalled = true;
+  }
+}
+
+
+/** Mock PrefsManager. Currently just returns hard-coded values.  */
+class MockPrefsManager {
+  backgroundShadingEnabled() {
+    return true;
+  }
+
+  focusRingColor() {
+    return FOCUS_RING_COLOR;
+  }
+
+  highlightColor() {
+    return HIGHLIGHT_COLOR;
+  }
+}
+
+/**
+ * Test fixture for ui_manager.js.
+ */
+SelectToSpeakUiManagerUnitTest = class extends SelectToSpeakE2ETest {
+  constructor() {
+    super();
+    this.mockAccessibilityPrivate = MockAccessibilityPrivate;
+    chrome.accessibilityPrivate = this.mockAccessibilityPrivate;
+
+    this.mockPrefsManager = null;
+    this.mockListener = null;
+    this.uiManager = null;
+  }
+
+  /** @override */
+  setUp() {
+    var runTest = this.deferRunTest(WhenTestDone.EXPECT);
+    (async () => {
+      let module = await import('/select_to_speak/ui_manager.js');
+      window.UiManager = module.UiManager;
+
+      module = await import('/select_to_speak/prefs_manager.js');
+      window.PrefsManager = module.PrefsManager;
+
+      module = await import('/select_to_speak/paragraph_utils.js');
+      window.ParagraphUtils = module.ParagraphUtils;
+
+      this.mockPrefsManager = new MockPrefsManager();
+      this.mockListener = new MockUiListener();
+      this.uiManager = new UiManager(this.mockPrefsManager, this.mockListener);
+
+      runTest();
+    })();
+  }
+};
+
+SYNC_TEST_F(
+    'SelectToSpeakUiManagerUnitTest', 'NextParagraphActionCallsListener',
+    function() {
+      this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction(
+          'nextParagraph');
+      assertTrue(this.mockListener.onNextParagraphRequestedCalled);
+    });
+
+SYNC_TEST_F(
+    'SelectToSpeakUiManagerUnitTest', 'PreviousParagraphActionCallsListener',
+    function() {
+      this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction(
+          'previousParagraph');
+      assertTrue(this.mockListener.onPreviousParagraphRequestedCalled);
+    });
+
+SYNC_TEST_F(
+    'SelectToSpeakUiManagerUnitTest', 'NextSentenceActionCallsListener',
+    function() {
+      this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction(
+          'nextSentence');
+      assertTrue(this.mockListener.onNextSentenceRequestedCalled);
+    });
+
+SYNC_TEST_F(
+    'SelectToSpeakUiManagerUnitTest', 'PreviousSentenceActionCallsListener',
+    function() {
+      this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction(
+          'previousSentence');
+      assertTrue(this.mockListener.onPreviousSentenceRequestedCalled);
+    });
+
+SYNC_TEST_F(
+    'SelectToSpeakUiManagerUnitTest', 'PauseActionCallsListener', function() {
+      this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction('pause');
+      assertTrue(this.mockListener.onPauseRequestedCalled);
+    });
+
+SYNC_TEST_F(
+    'SelectToSpeakUiManagerUnitTest', 'ResumeActionCallsListener', function() {
+      this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction('resume');
+      assertTrue(this.mockListener.onResumeRequestedCalled);
+    });
+
+SYNC_TEST_F(
+    'SelectToSpeakUiManagerUnitTest', 'ChangeSpeedActionCallsListener',
+    function() {
+      this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction(
+          'changeSpeed', 1.2);
+      assertEquals(1.2, this.mockListener.onChangeSpeedRequestedValue);
+    });
+
+SYNC_TEST_F(
+    'SelectToSpeakUiManagerUnitTest', 'ExitActionCallsListener', function() {
+      this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction('exit');
+      assertTrue(this.mockListener.onExitRequestedCalled);
+    });
+
+SYNC_TEST_F(
+    'SelectToSpeakUiManagerUnitTest', 'StateChangeCallsListener', function() {
+      this.mockAccessibilityPrivate.sendSelectToSpeakStateChangeRequest();
+      assertTrue(this.mockListener.onStateChangeRequestedCalled);
+    });
+
+SYNC_TEST_F('SelectToSpeakUiManagerUnitTest', 'SetSelectionRect', function() {
+  const selectionRect = {left: 0, top: 10, width: 400, height: 200};
+
+  // No focus rings to start.
+  let focusRings = this.mockAccessibilityPrivate.getFocusRings();
+  assertEquals(0, focusRings.length);
+
+  this.uiManager.setSelectionRect(selectionRect);
+
+  // Focus ring created to given rect.
+  focusRings = this.mockAccessibilityPrivate.getFocusRings();
+  assertEquals(1, focusRings.length);
+  assertEquals(1, focusRings[0].rects.length);
+  assertEquals(selectionRect, focusRings[0].rects[0]);
+  assertEquals(FOCUS_RING_COLOR, focusRings[0].color);
+});
+
+SYNC_TEST_F('SelectToSpeakUiManagerUnitTest', 'UpdatesUI', function() {
+  const textNode = {
+    role: 'staticText',
+    name: 'Test',
+    location: {left: 20, top: 10, width: 100, height: 50},
+  };
+  const nodeGroup = ParagraphUtils.buildNodeGroup(
+      [textNode], 0 /* index */, {splitOnLanguage: false});
+
+  // No focus rings to start.
+  let focusRings = this.mockAccessibilityPrivate.getFocusRings();
+  assertEquals(0, focusRings.length);
+
+  this.uiManager.update(
+      nodeGroup, textNode,
+      {showPanel: true, paused: false, speechRateMultiplier: 1});
+
+  // Focus ring created highlighting the text node.
+  focusRings = this.mockAccessibilityPrivate.getFocusRings();
+  assertEquals(1, focusRings.length);
+  assertEquals(1, focusRings[0].rects.length);
+  assertEquals(textNode.location, focusRings[0].rects[0]);
+  assertEquals(FOCUS_RING_COLOR, focusRings[0].color);
+
+  // Panel created with correct state.
+  const panelState = this.mockAccessibilityPrivate.getSelectToSpeakPanelState();
+  assertTrue(panelState.show);
+  assertEquals(textNode.location, panelState.anchor);
+  assertFalse(panelState.isPaused);
+  assertEquals(1, panelState.speed);
+});
+
+SYNC_TEST_F('SelectToSpeakUiManagerUnitTest', 'ClearsUI', function() {
+  // Start with a focus ring.
+  this.uiManager.setSelectionRect({left: 0, top: 10, width: 400, height: 200});
+  let focusRings = this.mockAccessibilityPrivate.getFocusRings();
+  assertEquals(1, focusRings.length);
+
+  this.uiManager.clear();
+
+  // Focus rings are gone.
+  focusRings = this.mockAccessibilityPrivate.getFocusRings();
+  assertEquals(1, focusRings.length);
+  assertEquals(0, focusRings[0].rects.length);
+
+  // Panel is not visible.
+  const panelState = this.mockAccessibilityPrivate.getSelectToSpeakPanelState();
+  assertFalse(panelState.show);
+});
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/emoji_picker/constants.js b/chrome/browser/resources/chromeos/emoji_picker/constants.js
index 1ad1a19..2d7edf3 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/constants.js
+++ b/chrome/browser/resources/chromeos/emoji_picker/constants.js
@@ -5,9 +5,13 @@
 // height and width should match the dialog size in EmojiPickerDialog.
 export const EMOJI_PICKER_HEIGHT = 390;
 export const EMOJI_PICKER_WIDTH = 340;
-export const EMOJI_SIZE = 32;
+export const EMOJI_ICON_SIZE = 32;
+export const GROUP_ICON_SIZE = 36;
 export const EMOJI_PER_ROW = 9;
+export const GROUP_PER_ROW = 9;
+export const EMOJI_PICKER_PADDING = 10;
 
 export const EMOJI_PICKER_HEIGHT_PX = `${EMOJI_PICKER_HEIGHT}px`;
 export const EMOJI_PICKER_WIDTH_PX = `${EMOJI_PICKER_WIDTH}px`;
-export const EMOJI_SIZE_PX = `${EMOJI_SIZE}px`;
+export const EMOJI_SIZE_PX = `${EMOJI_ICON_SIZE}px`;
+export const EMOJI_PICKER_PADDING_PX = `${EMOJI_PICKER_PADDING}px`;
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
index e632ffa1..52c4dae 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
@@ -2,6 +2,7 @@
   :host {
     /* values overriden in Javascript from constants.js */
     --emoji-picker-height: 0;
+    --emoji-picker-padding: 0;
     --emoji-picker-width: 0;
     --emoji-size: 0;
     --emoji-per-row: 0;
@@ -11,10 +12,10 @@
     display: flex;
     flex-direction: column;
     font-family: 'Roboto', sans-serif;
-    height: calc(var(--emoji-picker-height) - 20px);
+    height: calc(var(--emoji-picker-height) - 2 * var(--emoji-picker-padding));
     margin-top: 0;
-    padding: 10px;
-    width: calc(var(--emoji-picker-width) - 20px);
+    padding: var(--emoji-picker-padding);
+    width: calc(var(--emoji-picker-width) - 2 * var(--emoji-picker-padding));
   }
 
   #searchContainer,
@@ -30,9 +31,12 @@
     display: flex;
     flex-shrink: 0;
     overflow-x: scroll;
-    /* scroll-behavior: smooth; */
+    scroll-behavior: smooth;
     width: 100%;
   }
+  #tabs::-webkit-scrollbar {
+    display: none;
+  }
 
   #tabs::-webkit-scrollbar {
     display: none;
@@ -41,7 +45,25 @@
   #groups {
     flex-grow: 1;
     overflow-y: scroll;
-    /* scroll-behavior: smooth; */
+  }
+
+  .chevron {
+    background-color: var(--cr-card-background-color);
+    height: 36px;
+    margin: 0;
+    padding: 0;
+    position: absolute;
+    width: 36px;
+    z-index: 2;
+  }
+
+  #rightChevron {
+    left: calc(36px*8 + var(--emoji-picker-padding));
+  }
+
+  #leftChevron {
+    display: none;
+    left: 10px;
   }
 
   .divider {
@@ -73,7 +95,14 @@
 
   <div class="sr-only" role="heading" aria-level="1">Emoji Group Buttons</div>
 
-  <div id="tabs">
+  <div id="tabs" on-scroll="onGroupsScroll">
+    <cr-icon-button
+      id="leftChevron"
+      class="chevron"
+      on-click="onLeftChevronClick"
+      iron-icon="emoji_picker:keyboard_arrow_left"
+      >
+      </cr-icon-button>
     <template is="dom-repeat" items="[[emojiGroupTabs]]">
       <emoji-group-button
           data-group$="[[item.groupId]]" group-id="[[item.groupId]]"
@@ -81,6 +110,26 @@
           name="[[item.name]]">
       </emoji-group-button>
     </template>
+    <!--Fake group button to increase maximum scrolling, need an icon to render
+      as invisible-->
+      <emoji-group-button id="fake" icon="[[emojiGroupTabs[0].icon]]">
+      </emoji-group-button>
+      <emoji-group-button id="fake" icon="[[emojiGroupTabs[0].icon]]">
+      </emoji-group-button>
+      <emoji-group-button id="fake" icon="[[emojiGroupTabs[0].icon]]">
+      </emoji-group-button>
+      <emoji-group-button id="fake" icon="[[emojiGroupTabs[0].icon]]">
+      </emoji-group-button>
+      <emoji-group-button id="RightChevronScrollTarget"
+        icon="[[emojiGroupTabs[0].icon]]">
+      </emoji-group-button>
+   <cr-icon-button
+    id="rightChevron"
+      class="chevron"
+    on-click="onRightChevronClick"
+    iron-icon="emoji_picker:keyboard_arrow_right"
+    >
+    </cr-icon-button>
   </div>
 
   <div class="sr-only" role="heading" aria-level="1">Emoji Groups</div>
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js
index 19b96e5..0b31861 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js
@@ -11,7 +11,7 @@
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {afterNextRender, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {EMOJI_PER_ROW, EMOJI_PICKER_HEIGHT_PX, EMOJI_PICKER_WIDTH_PX, EMOJI_SIZE, EMOJI_SIZE_PX} from './constants.js';
+import {EMOJI_ICON_SIZE, EMOJI_PER_ROW, EMOJI_PICKER_HEIGHT_PX, EMOJI_PICKER_PADDING_PX, EMOJI_PICKER_WIDTH_PX, EMOJI_SIZE_PX, GROUP_ICON_SIZE, GROUP_PER_ROW} from './constants.js';
 import {EmojiButton} from './emoji_button.js';
 import {createCustomEvent, EMOJI_BUTTON_CLICK, EMOJI_DATA_LOADED, EMOJI_VARIANTS_SHOWN, EmojiVariantsShownEvent, GROUP_BUTTON_CLICK} from './events.js';
 import {RecentEmojiStore} from './store.js';
@@ -134,8 +134,28 @@
     /** @private {?number} */
     this.scrollTimeout = null;
 
+    /** @private {?number} */
+    this.groupScrollTimeout = null;
+
     /** @private {?EmojiButton} */
     this.activeVariant = null;
+
+    /** @private {boolean} */
+    this.autoScrollingToGroup = false;
+
+    // basic click handlers
+    this.addEventListener(
+        GROUP_BUTTON_CLICK, ev => this.selectGroup(ev.detail.group));
+    this.addEventListener(
+        EMOJI_BUTTON_CLICK,
+        ev => this.insertEmoji(ev.detail.emoji, ev.detail.isVariant));
+
+    // variant popup related handlers
+    this.addEventListener(
+        EMOJI_VARIANTS_SHOWN,
+        ev => this.onShowEmojiVariants(
+            /** @type {!EmojiVariantsShownEvent} */ (ev)));
+    this.addEventListener('click', () => this.hideEmojiVariants());
   }
 
   ready() {
@@ -151,22 +171,7 @@
       '--emoji-picker-height': EMOJI_PICKER_HEIGHT_PX,
       '--emoji-size': EMOJI_SIZE_PX,
       '--emoji-per-row': EMOJI_PER_ROW,
-    });
-
-    afterNextRender(this, () => {
-      // basic click handlers
-      this.addEventListener(
-          GROUP_BUTTON_CLICK, ev => this.selectGroup(ev.detail.group));
-      this.addEventListener(
-          EMOJI_BUTTON_CLICK,
-          ev => this.insertEmoji(ev.detail.emoji, ev.detail.isVariant));
-
-      // variant popup related handlers
-      this.addEventListener(
-          EMOJI_VARIANTS_SHOWN,
-          ev => this.onShowEmojiVariants(
-              /** @type {!EmojiVariantsShownEvent} */ (ev)));
-      this.addEventListener('click', () => this.hideEmojiVariants());
+      '--emoji-picker-padding': EMOJI_PICKER_PADDING_PX,
     });
   }
 
@@ -195,7 +200,7 @@
         this.shadowRoot.querySelector(`div[data-group="${newGroup}"]`);
     group.querySelector('emoji-group')
         .shadowRoot.querySelector('emoji-button')
-        .focusButton({preventScroll: true});
+        .focusButton();
     group.scrollIntoView();
   }
 
@@ -206,9 +211,55 @@
     if (this.scrollTimeout) {
       clearTimeout(this.scrollTimeout);
     }
-    this.scrollTimeout = setTimeout(this.updateActiveGroup.bind(this), 100);
+    this.scrollTimeout = setTimeout(this.updateActiveGroup.bind(this), 250);
   }
 
+  onRightChevronClick() {
+    this.shadowRoot.getElementById('tabs').scrollLeft =
+        GROUP_ICON_SIZE * (GROUP_PER_ROW + 1);
+    this.scrollToGroup(GROUP_TABS[GROUP_PER_ROW - 2].groupId);
+  }
+
+  onLeftChevronClick() {
+    this.shadowRoot.getElementById('tabs').scrollLeft = 0;
+    // TODO(crbug/1152237): need to handle case where recent is empty
+    this.scrollToGroup(GROUP_TABS[0].groupId);
+  }
+
+  /**
+   * @param {string} newGroup The group ID to scroll to
+   */
+  scrollToGroup(newGroup) {
+    // TODO(crbug/1152237): This should use behaviour:'smooth', but when you do
+    // that it doesn't scroll.
+    this.shadowRoot.querySelector(`div[data-group="${newGroup}"]`)
+        .scrollIntoView();
+  }
+
+  onGroupsScroll() {
+    this.updateChevrons();
+  }
+
+  /**
+   * @private
+   */
+  updateChevrons() {
+    if (this.shadowRoot.getElementById('tabs').scrollLeft > GROUP_ICON_SIZE) {
+      this.shadowRoot.getElementById('leftChevron').style.display = 'flex';
+    } else {
+      this.shadowRoot.getElementById('leftChevron').style.display = 'none';
+    }
+    // 1 less because we need to allow room for the chevrons
+    if (this.shadowRoot.getElementById('tabs').scrollLeft +
+            GROUP_ICON_SIZE * GROUP_PER_ROW <
+        GROUP_ICON_SIZE * (GROUP_TABS.length + 1)) {
+      this.shadowRoot.getElementById('rightChevron').style.display = 'flex';
+    } else {
+      this.shadowRoot.getElementById('rightChevron').style.display = 'none';
+    }
+  }
+
+
   updateActiveGroup() {
     // no need to update scroll state if search is showing.
     if (this.search)
@@ -229,16 +280,31 @@
     assert(activeGroup, 'no group element was activated');
     const activeGroupId = activeGroup.dataset.group;
 
+    let index = 0;
     // set active to true for selected group and false for others.
     this.emojiGroupTabs.forEach((g, i) => {
       const isActive = g.groupId === activeGroupId;
+      if (isActive) {
+        index = i;
+      }
       this.set(['emojiGroupTabs', i, 'active'], isActive);
     });
 
-    // scroll the active group button into view on the tab bar.
-    this.shadowRoot
-        .querySelector(`emoji-group-button[data-group="${activeGroupId}"]`)
-        .scrollIntoView();
+    // Maybe group icons tab scroll to match active group
+    if (this.shadowRoot.getElementById('tabs').scrollLeft >
+        GROUP_ICON_SIZE * index) {
+      this.shadowRoot.getElementById('tabs').scrollLeft =
+          GROUP_ICON_SIZE * (index - 1);
+    }
+    // Maybe increase icons tab scroll to match active group
+    if (this.shadowRoot.getElementById('tabs').scrollLeft +
+            GROUP_ICON_SIZE * (GROUP_PER_ROW - 2) <
+        GROUP_ICON_SIZE * (index)) {
+      // 3 = 1 for 1 based index + 2 for chevrons (left and right can display at
+      // the same time).
+      this.shadowRoot.getElementById('tabs').scrollLeft =
+          GROUP_ICON_SIZE * (index + 3 - GROUP_PER_ROW);
+    }
   }
 
 
@@ -278,7 +344,7 @@
     const rect = variants.getBoundingClientRect();
     const overflowWidth = rect.x + rect.width - pickerRect.width;
     // shift left by overflowWidth rounded up to next multiple of EMOJI_SIZE.
-    const shift = EMOJI_SIZE * Math.ceil(overflowWidth / EMOJI_SIZE);
+    const shift = EMOJI_ICON_SIZE * Math.ceil(overflowWidth / EMOJI_ICON_SIZE);
     // negative value means we are already within bounds, so no shift needed.
     variants.style.marginLeft = `-${Math.max(shift, 0)}px`;
   }
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_variants.js b/chrome/browser/resources/chromeos/emoji_picker/emoji_variants.js
index 9fc6b9d..63aeb1f 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_variants.js
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_variants.js
@@ -36,7 +36,6 @@
 function partitionArray(array, subarrayLengths) {
   const subarrays = [];
   let used = 0;
-
   for (const len of subarrayLengths) {
     if (len < 0) {
       used += -len;
@@ -122,7 +121,6 @@
       return [5, 5, 5, 5, 5];
     }
 
-    console.error('unimplemented variation: ', variants);
     return [];
   }
 
diff --git a/chrome/browser/resources/chromeos/emoji_picker/icons.html b/chrome/browser/resources/chromeos/emoji_picker/icons.html
index 8b5a7325..b87cf1b 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/icons.html
+++ b/chrome/browser/resources/chromeos/emoji_picker/icons.html
@@ -13,6 +13,8 @@
     <svg id="emoji_transportation" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M20.57 10.66c-.14-.4-.52-.66-.97-.66h-7.19c-.46 0-.83.26-.98.66L10 14.77l.01 5.51c0 .38.31.72.69.72h.62c.38 0 .68-.38.68-.76V19h8v1.24c0 .38.31.76.69.76h.61c.38 0 .69-.34.69-.72l.01-1.37v-4.14l-1.43-4.11zm-8.16.34h7.19l1.03 3h-9.25l1.03-3zM12 17c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm8 0c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z"/><path d="M14 9h1V3H7v5H2v13h1V9h5V4h6z"/><path d="M5 11h2v2H5zM10 5h2v2h-2zM5 15h2v2H5zM5 19h2v2H5z"/></svg>
     <svg id="flag" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M14.4 6L14 4H5v17h2v-7h5.6l.4 2h7V6z"/></svg>
     <svg id="insert_emoticon" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z"/></svg>
+    <svg id="keyboard_arrow_left" xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"/></svg>
+    <svg id="keyboard_arrow_right" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg>
     <svg id="schedule" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/><path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"/></svg>
   </defs>
 </iron-iconset-svg>
diff --git a/chrome/browser/resources/chromeos/login/components/network_select_login.js b/chrome/browser/resources/chromeos/login/components/network_select_login.js
index d21900f..90d41a4 100644
--- a/chrome/browser/resources/chromeos/login/components/network_select_login.js
+++ b/chrome/browser/resources/chromeos/login/components/network_select_login.js
@@ -65,10 +65,7 @@
        */
       showTechnologyBadge_: {
         type: Boolean,
-        value() {
-          return loadTimeData.valueExists('showTechnologyBadge') &&
-              loadTimeData.getBoolean('showTechnologyBadge');
-        }
+        value: false,
       }
     },
 
diff --git a/chrome/browser/resources/chromeos/login/gaia_dialog.js b/chrome/browser/resources/chromeos/login/gaia_dialog.js
index 00dee20..c8ffab1 100644
--- a/chrome/browser/resources/chromeos/login/gaia_dialog.js
+++ b/chrome/browser/resources/chromeos/login/gaia_dialog.js
@@ -212,9 +212,11 @@
       },
       'dialogShown': (e) => {
         this.navigationEnabled = false;
+        chrome.send('enableShelfButtons', [false]);
       },
       'dialogHidden': (e) => {
         this.navigationEnabled = true;
+        chrome.send('enableShelfButtons', [true]);
       },
       'exit': (e) => {
         this.fire('exit', e.detail);
diff --git a/chrome/browser/resources/download_shelf/BUILD.gn b/chrome/browser/resources/download_shelf/BUILD.gn
index a6d8ba6..49e90490 100644
--- a/chrome/browser/resources/download_shelf/BUILD.gn
+++ b/chrome/browser/resources/download_shelf/BUILD.gn
@@ -3,15 +3,60 @@
 # found in the LICENSE file.
 
 import("//chrome/common/features.gni")
+import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/grit/grit_rule.gni")
+import("//tools/grit/preprocess_if_expr.gni")
+import("//tools/polymer/html_to_js.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
 import("//ui/webui/webui_features.gni")
 
+preprocess_folder = "preprocessed"
+preprocess_manifest = "preprocessed_manifest.json"
+preprocess_web_components_manifest = "preprocessed_gen_manifest.json"
+preprocess_mojo_manifest = "preprocessed_mojo_manifest.json"
+
 generate_grd("build_grd") {
   grd_prefix = "download_shelf"
   out_grd = "$target_gen_dir/resources.grd"
   input_files = [ "download_shelf.html" ]
   input_files_base_dir = rebase_path(".", "//")
+  deps = [
+    ":preprocess",
+    ":preprocess_mojo",
+    ":preprocess_web_components",
+  ]
+  manifest_files = [
+    "$target_gen_dir/$preprocess_manifest",
+    "$target_gen_dir/$preprocess_mojo_manifest",
+    "$target_gen_dir/$preprocess_web_components_manifest",
+  ]
+}
+
+preprocess_if_expr("preprocess") {
+  in_folder = "./"
+  out_folder = "$target_gen_dir/$preprocess_folder"
+  out_manifest = "$target_gen_dir/$preprocess_manifest"
+  in_files = [ "download_shelf_api_proxy.js" ]
+}
+
+preprocess_if_expr("preprocess_mojo") {
+  deps = [ "//chrome/browser/ui/webui/download_shelf:mojo_bindings_js" ]
+  in_folder =
+      "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/download_shelf/"
+  out_folder = "$target_gen_dir/$preprocess_folder"
+  out_manifest = "$target_gen_dir/$preprocess_mojo_manifest"
+  in_files = [ "download_shelf.mojom-webui.js" ]
+}
+
+preprocess_if_expr("preprocess_web_components") {
+  deps = [ ":web_components" ]
+  in_folder = target_gen_dir
+  out_folder = "$target_gen_dir/$preprocess_folder"
+  out_manifest = "$target_gen_dir/$preprocess_web_components_manifest"
+  in_files = [
+    "app.js",
+    "download_item.js",
+  ]
 }
 
 grit("resources") {
@@ -30,3 +75,43 @@
   ]
   output_dir = "$root_gen_dir/chrome"
 }
+
+js_type_check("closure_compile") {
+  closure_flags = default_closure_args + mojom_js_args + [
+                    "js_module_root=" + rebase_path(".", root_build_dir),
+                    "js_module_root=" + rebase_path(
+                            "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/download_shelf",
+                            root_build_dir),
+                  ]
+  deps = [
+    ":app",
+    ":download_item",
+    ":download_shelf_api_proxy",
+  ]
+}
+
+js_library("download_shelf_api_proxy") {
+  deps = [
+    "//chrome/browser/ui/webui/download_shelf:mojo_bindings_webui_js",
+    "//ui/webui/resources/js:cr.m",
+  ]
+  externs_list = [ "$externs_path/chrome_extensions.js" ]
+}
+
+js_library("download_item") {
+  deps = [ "//ui/webui/resources/js:custom_element" ]
+}
+
+js_library("app") {
+  deps = [
+    ":download_item",
+    "//ui/webui/resources/js:custom_element",
+  ]
+}
+
+html_to_js("web_components") {
+  js_files = [
+    "app.js",
+    "download_item.js",
+  ]
+}
diff --git a/chrome/browser/resources/download_shelf/app.html b/chrome/browser/resources/download_shelf/app.html
new file mode 100644
index 0000000..01804cc
--- /dev/null
+++ b/chrome/browser/resources/download_shelf/app.html
@@ -0,0 +1 @@
+<div id="downloadList"></div>
diff --git a/chrome/browser/resources/download_shelf/app.js b/chrome/browser/resources/download_shelf/app.js
new file mode 100644
index 0000000..3f4ba51
--- /dev/null
+++ b/chrome/browser/resources/download_shelf/app.js
@@ -0,0 +1,40 @@
+// 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 './download_item.js';
+import {CustomElement} from 'chrome://resources/js/custom_element.js';
+import {DownloadShelfApiProxy, DownloadShelfApiProxyImpl} from './download_shelf_api_proxy.js';
+
+export class DownloadShelfAppElement extends CustomElement {
+  static get template() {
+    return `{__html_template__}`;
+  }
+
+  constructor() {
+    super();
+
+    /** @private {!DownloadShelfApiProxy} */
+    this.apiProxy_ = DownloadShelfApiProxyImpl.getInstance();
+
+    this.apiProxy_.getDownloads().then(downloadItems => {
+      const downloadList = this.$('#downloadList');
+      for (const item of downloadItems) {
+        const downloadElement = document.createElement('download-item');
+        downloadElement.item = item;
+        downloadElement.addEventListener('click', this.onItemClick_);
+        downloadList.appendChild(downloadElement);
+      }
+    });
+  }
+
+  /**
+   * @param {!Event} e
+   * @private
+   */
+  onItemClick_(e) {
+    // TODO: access download item from e.currentTarget.item.
+  }
+}
+
+customElements.define('download-shelf-app', DownloadShelfAppElement);
diff --git a/chrome/browser/resources/download_shelf/download_item.html b/chrome/browser/resources/download_shelf/download_item.html
new file mode 100644
index 0000000..3cd42d89
--- /dev/null
+++ b/chrome/browser/resources/download_shelf/download_item.html
@@ -0,0 +1 @@
+<div id="filename"></div>
diff --git a/chrome/browser/resources/download_shelf/download_item.js b/chrome/browser/resources/download_shelf/download_item.js
new file mode 100644
index 0000000..30cbedff
--- /dev/null
+++ b/chrome/browser/resources/download_shelf/download_item.js
@@ -0,0 +1,36 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview UI element of a download item.
+ */
+
+import {CustomElement} from 'chrome://resources/js/custom_element.js';
+
+export class DownloadItemElement extends CustomElement {
+  static get template() {
+    return `{__html_template__}`;
+  }
+
+  constructor() {
+    super();
+
+    /** @private {Object} */
+    this.item_;
+  }
+
+  set item(value) {
+    this.item_ = value;
+  }
+
+  get item() {
+    return this.item_;
+  }
+
+  connectedCallback() {
+    this.$('#filename').innerText = this.item_.filename;
+  }
+}
+
+customElements.define('download-item', DownloadItemElement);
diff --git a/chrome/browser/resources/download_shelf/download_shelf.html b/chrome/browser/resources/download_shelf/download_shelf.html
index 20b4876..6f4a930 100644
--- a/chrome/browser/resources/download_shelf/download_shelf.html
+++ b/chrome/browser/resources/download_shelf/download_shelf.html
@@ -1,6 +1,11 @@
 <!doctype html>
-<html>
+<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<head>
+  <meta charset="utf-8">
+  <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
+</head>
 <body>
-Download Shelf
+  <download-shelf-app></download-shelf-app>
+  <script type="module" src="app.js"></script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/chrome/browser/resources/download_shelf/download_shelf_api_proxy.js b/chrome/browser/resources/download_shelf/download_shelf_api_proxy.js
new file mode 100644
index 0000000..2afbd2b
--- /dev/null
+++ b/chrome/browser/resources/download_shelf/download_shelf_api_proxy.js
@@ -0,0 +1,53 @@
+// 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 {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+
+import {PageCallbackRouter, PageHandlerFactory, PageHandlerRemote} from './download_shelf.mojom-webui.js';
+
+/** @interface */
+export class DownloadShelfApiProxy {
+  /** @return {!PageCallbackRouter} */
+  getCallbackRouter() {}
+
+  /**
+   * @return {!Promise<!Array<!chrome.downloads.DownloadItem>>} callback
+   */
+  getDownloads() {}
+}
+
+/** @implements {DownloadShelfApiProxy} */
+export class DownloadShelfApiProxyImpl {
+  constructor() {
+    /** @type {!PageCallbackRouter} */
+    this.callbackRouter = new PageCallbackRouter();
+
+    /** @type {!PageHandlerRemote} */
+    this.handler = new PageHandlerRemote();
+
+    const factory = PageHandlerFactory.getRemote();
+    factory.createPageHandler(
+        this.callbackRouter.$.bindNewPipeAndPassRemote(),
+        this.handler.$.bindNewPipeAndPassReceiver());
+  }
+
+  /** @override */
+  getCallbackRouter() {
+    return this.callbackRouter;
+  }
+
+  /** @override */
+  getDownloads() {
+    return new Promise(resolve => {
+      chrome.downloads.search(
+          {
+            orderBy: ['-startTime'],
+            limit: 100,
+          },
+          resolve);
+    });
+  }
+}
+
+addSingletonGetter(DownloadShelfApiProxyImpl);
diff --git a/chrome/browser/resources/feedback_webui/html/default.html b/chrome/browser/resources/feedback_webui/html/default.html
index 1dcda05..a13d03c 100644
--- a/chrome/browser/resources/feedback_webui/html/default.html
+++ b/chrome/browser/resources/feedback_webui/html/default.html
@@ -12,6 +12,8 @@
   <script src="chrome://resources/js/load_time_data.js"></script>
   <script src="chrome://resources/js/assert.js"></script>
   <script src="chrome://resources/js/util.js"></script>
+  <script src="chrome://resources/js/promise_resolver.js"></script>
+  <script src="chrome://resources/js/cr.js"></script>
   <script src="../strings.js"></script>
   <script src="../js/feedback_util.js"></script>
   <script src="../js/take_screenshot.js"></script>
diff --git a/chrome/browser/resources/feedback_webui/html/sys_info.html b/chrome/browser/resources/feedback_webui/html/sys_info.html
index 32a2eb4c..8af33fe7 100644
--- a/chrome/browser/resources/feedback_webui/html/sys_info.html
+++ b/chrome/browser/resources/feedback_webui/html/sys_info.html
@@ -7,8 +7,10 @@
     <link rel="stylesheet" href="chrome://resources/css/spinner.css">
     <link rel="stylesheet" href="../../about_sys/about_sys.css">
     <link rel="stylesheet" href="../css/sys_info.css">
+    <script src="chrome://resources/js/load_time_data.js"></script>
     <script src="chrome://resources/js/assert.js"></script>
     <script src="chrome://resources/js/util.js"></script>
+    <script src="../strings.js"></script>
     <script src="../js/sys_info.js"></script>
   </head>
   <body>
diff --git a/chrome/browser/resources/feedback_webui/js/BUILD.gn b/chrome/browser/resources/feedback_webui/js/BUILD.gn
index 264c833..3278ceb 100644
--- a/chrome/browser/resources/feedback_webui/js/BUILD.gn
+++ b/chrome/browser/resources/feedback_webui/js/BUILD.gn
@@ -9,9 +9,9 @@
   deps = [
     # TODO(crbug.com/1167223): Fix and enable remaining js_library() targets.
     #":assistant_logs_info",
+    #":sys_info",
     ":feedback",
     ":feedback_util",
-    ":sys_info",
     ":take_screenshot",
 
     #":topbar_handlers",
diff --git a/chrome/browser/resources/feedback_webui/js/feedback.js b/chrome/browser/resources/feedback_webui/js/feedback.js
index 59c2f1c0..7a49af2 100644
--- a/chrome/browser/resources/feedback_webui/js/feedback.js
+++ b/chrome/browser/resources/feedback_webui/js/feedback.js
@@ -2,21 +2,132 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// TODO(crbug.com/1167223
-// dummy data, use  temporarily until a message handler is implemented
-// real data shall be retrieved from backend or be passed via query string
-const feedbackInfo = {
-  categoryTag: '',
-  description: '',
-  descriptionPlaceholder: 'Please enter a description of your issue',
-  flow: 'regular',
-  pageUrl:
-      'https://superuser.com/questions/777213/copy-json-from-console-log-in-developer-tool-to-clipboard',
+/**
+ * The object will be manipulated by feedbackHelper
+ *
+ * @type {chrome.feedbackPrivate.FeedbackInfo}
+ */
+let feedbackInfo = {
+  assistantDebugInfoAllowed: false,
+  attachedFile: undefined,
+  attachedFileBlobUuid: undefined,
+  categoryTag: undefined,
+  description: '...',
+  descriptionPlaceholder: undefined,
+  email: undefined,
+  flow: chrome.feedbackPrivate.FeedbackFlow.REGULAR,
+  fromAssistant: false,
+  includeBluetoothLogs: false,
+  pageUrl: undefined,
+  sendHistograms: undefined,
   systemInformation: [],
   useSystemWindowFrame: false,
-  screenshot: {}
 };
 
+
+class FeedbackHelper {
+  constructor() {
+    /**
+     * @type {boolean}
+     */
+    this.systemInformationLoaded = false;
+  }
+
+  getFeedbackInfo() {
+    // If the getFeedbackInfo is implemented by a message handler, then the
+    // data returned from it will be used. Otherwise, the default data is used.
+    // Currently, if a user visits chrome://feedback directly, the message
+    // handler will not be available. In this case, we should still allow the
+    // user submit a feedback.
+    return new Promise(
+        resolve => cr.sendWithPromise('getFeedbackInfo')
+                       .then(resolve, resolve({
+                               assistantDebugInfoAllowed: false,
+                               attachedFile: undefined,
+                               attachedFileBlobUuid: undefined,
+                               categoryTag: undefined,
+                               description: undefined,
+                               descriptionPlaceholder: undefined,
+                               email: undefined,
+                               flow: 'regular',
+                               fromAssistant: false,
+                               includeBluetoothLogs: false,
+                               pageUrl: undefined,
+                               screenshot: {},
+                               systemInformation: [],
+                               useSystemWindowFrame: false,
+                             })));
+  }
+
+  getSystemInformation() {
+    return new Promise(
+        resolve => chrome.feedbackPrivate.getSystemInformation(resolve));
+  }
+
+  getFullSystemInformation() {
+    return new Promise(resolve => {
+      if (this.systemInformationLoaded) {
+        resolve(feedbackInfo.systemInformation);
+      } else {
+        this.getSystemInformation().then(function(sysInfo) {
+          if (feedbackInfo.systemInformation) {
+            feedbackInfo.systemInformation =
+                feedbackInfo.systemInformation.concat(sysInfo);
+          } else {
+            feedbackInfo.systemInformation = sysInfo;
+          }
+          this.systemInformationLoaded = true;
+          resolve(feedbackInfo.systemInformation);
+        });
+      }
+    });
+  }
+
+  getUserEmail() {
+    return new Promise(resolve => chrome.feedbackPrivate.getUserEmail(resolve));
+  }
+
+  /**
+   * @param {boolean} useSystemInfo
+   */
+  sendFeedbackReport(useSystemInfo) {
+    const ID = Math.round(Date.now() / 1000);
+    const FLOW = feedbackInfo.flow;
+    if (!useSystemInfo) {
+      this.systemInformationLoaded = false;
+      feedbackInfo.systemInformation = [];
+    }
+
+    chrome.feedbackPrivate.sendFeedback(
+        feedbackInfo, function(result, landingPageType) {
+          if (result == chrome.feedbackPrivate.Status.SUCCESS) {
+            if (FLOW != chrome.feedbackPrivate.FeedbackFlow.LOGIN &&
+                landingPageType !=
+                    chrome.feedbackPrivate.LandingPageType.NO_LANDING_PAGE) {
+              const landingPage = landingPageType ==
+                      chrome.feedbackPrivate.LandingPageType.NORMAL ?
+                  FEEDBACK_LANDING_PAGE :
+                  FEEDBACK_LANDING_PAGE_TECHSTOP;
+              window.open(landingPage, '_blank');
+            }
+          } else {
+            console.warn(
+                'Feedback: Report for request with ID ' + ID +
+                ' will be sent later.');
+          }
+          if (FLOW == chrome.feedbackPrivate.FeedbackFlow.LOGIN) {
+            chrome.feedbackPrivate.loginFeedbackComplete();
+          }
+        });
+  }
+}
+
+/**
+ * @type {FeedbackHelper}
+ * @const
+ */
+const feedbackHelper = new FeedbackHelper();
+
 /** @type {number}
  * @const
  */
@@ -50,6 +161,9 @@
  */
 const SYSINFO_WINDOW_ID = 'sysinfo_window';
 
+/**
+ * @type {Blob}
+ */
 let attachedFileBlob = null;
 const lastReader = null;
 
@@ -138,7 +252,7 @@
 function clearAttachedFile() {
   $('custom-file-container').hidden = true;
   attachedFileBlob = null;
-  feedbackInfo.attachedFile = null;
+  feedbackInfo.attachedFile = undefined;
   $('attach-file').hidden = false;
 }
 
@@ -233,11 +347,10 @@
 
   // Prevent double clicking from sending additional reports.
   $('send-report-button').disabled = true;
-  console.log('Feedback: Sending report');
   if (!feedbackInfo.attachedFile && attachedFileBlob) {
     feedbackInfo.attachedFile = {
       name: $('attach-file').value,
-      data: attachedFileBlob
+      data: attachedFileBlob,
     };
   }
 
@@ -270,7 +383,7 @@
   }
   if ($('performance-info-checkbox') == null ||
       !($('performance-info-checkbox').checked)) {
-    feedbackInfo.traceId = null;
+    feedbackInfo.traceId = undefined;
   }
   // </if>
 
@@ -289,16 +402,14 @@
     // For apps that still use a string value as the |productId|, we must clear
     // that value since the API uses an integer value, and a conflict in data
     // types will cause the report to fail to be sent.
-    productId = null;
+    productId = undefined;
   }
   feedbackInfo.productId = productId;
 
-  // Request sending the report, show the landing page (if allowed), and close
-  // this window right away. The FeedbackRequest object that represents this
-  // report will take care of sending the report in the background.
+  // Request sending the report, show the landing page (if allowed)
+  feedbackHelper.sendFeedbackReport(useSystemInfo);
   // TODO(crbug.com/1167223): Implement this.
-  // sendFeedbackReport(useSystemInfo);
-  scheduleWindowClose();
+  // scheduleWindowClose();
   return true;
 }
 
@@ -435,7 +546,7 @@
       });
     });
 
-    chrome.feedbackPrivate.getUserEmail(function(email) {
+    feedbackHelper.getUserEmail().then(function(email) {
       // Never add an empty option.
       if (!email) {
         return;
@@ -491,19 +602,7 @@
             window.open('/html/sys_info.html', SYSINFO_WINDOW_ID, params);
 
         if (sysWin) {
-          sysWin.window.getFullSystemInfo = function(callback) {
-            chrome.feedbackPrivate.getSystemInformation(function(sysInfo) {
-              if (feedbackInfo.systemInformation) {
-                callback(sysInfo.concat(feedbackInfo.systemInformation));
-              } else {
-                callback(sysInfo);
-              }
-            });
-          };
-
-          sysWin.window.getLoadTimeData = function() {
-            return loadTimeData;
-          };
+          sysWin.window.getFullSystemInfo = feedbackHelper.getSystemInformation;
         }
       };
 
@@ -585,26 +684,21 @@
   };
 
   window.addEventListener('DOMContentLoaded', function() {
-    // TODO(crbug.com/1167223
-    // feedbackInfo will be retrieved from backend or
-    // be passed as query string. This will be addressed via
-    // following CL.
-    // chrome.feedbackPrivate.getFeedbackInfo(function(feedbackInfo) {
-    //   applyData(feedbackInfo);
-    // });
-    applyData(feedbackInfo);
+    feedbackHelper.getFeedbackInfo().then(function(data) {
+      feedbackInfo = data;
+      applyData(feedbackInfo);
 
-
-    // Setup our event handlers.
-    $('attach-file').addEventListener('change', onFileSelected);
-    $('attach-file').addEventListener('click', onOpenFileDialog);
-    $('send-report-button').onclick = sendReport;
-    $('cancel-button').onclick = cancel;
-    $('remove-attached-file').onclick = clearAttachedFile;
-    // <if expr="chromeos">
-    $('performance-info-checkbox')
-        .addEventListener('change', performanceFeedbackChanged);
-    // </if>
+      // Setup our event handlers.
+      $('attach-file').addEventListener('change', onFileSelected);
+      $('attach-file').addEventListener('click', onOpenFileDialog);
+      $('send-report-button').onclick = sendReport;
+      $('cancel-button').onclick = cancel;
+      $('remove-attached-file').onclick = clearAttachedFile;
+      // <if expr="chromeos">
+      $('performance-info-checkbox')
+          .addEventListener('change', performanceFeedbackChanged);
+      // </if>
+    });
   });
 }
 
diff --git a/chrome/browser/resources/feedback_webui/js/sys_info.js b/chrome/browser/resources/feedback_webui/js/sys_info.js
index 09b1ddb..0e8418c9 100644
--- a/chrome/browser/resources/feedback_webui/js/sys_info.js
+++ b/chrome/browser/resources/feedback_webui/js/sys_info.js
@@ -227,6 +227,6 @@
  * Initializes the page when the window is loaded.
  */
 window.onload = function() {
-  // TODO(crbug.com/1167223): Implement this.
-  // getFullSystemInfo(createTable);
+  // The getFullSystemInfo promise is set from the parent page
+  getFullSystemInfo().then(createTable);
 };
diff --git a/chrome/browser/resources/memories/BUILD.gn b/chrome/browser/resources/memories/BUILD.gn
index 98622b3..3232fd0 100644
--- a/chrome/browser/resources/memories/BUILD.gn
+++ b/chrome/browser/resources/memories/BUILD.gn
@@ -6,6 +6,7 @@
 import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/grit/grit_rule.gni")
 import("//tools/grit/preprocess_if_expr.gni")
+import("//tools/polymer/html_to_js.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
 
 js_type_check("closure_compile") {
@@ -14,10 +15,16 @@
   deps = [ ":memories" ]
 }
 
-js_library("memories") {
+js_library("app") {
   deps = [
     ":browser_proxy",
+    ":memory_card",
+    ":page_thumbnail",
+    ":utils",
     "//chrome/browser/ui/webui/memories:mojo_bindings_webui_js",
+    "//mojo/public/mojom/base:base_webui_js",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//url/mojom:url_mojom_origin_webui_js",
   ]
 }
 
@@ -28,9 +35,47 @@
   ]
 }
 
+js_library("memories") {
+  deps = [ ":app" ]
+}
+
+js_library("memory_card") {
+  deps = [
+    "//components/memories/core:mojo_bindings_webui_js",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+  ]
+}
+
+js_library("page_thumbnail") {
+  deps = [
+    ":utils",
+    "//components/memories/core:mojo_bindings_webui_js",
+    "//mojo/public/mojom/base:base_webui_js",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//url/mojom:url_mojom_origin_webui_js",
+  ]
+}
+
+js_library("utils") {
+  deps = [ "//mojo/public/mojom/base:base_webui_js" ]
+}
+
+html_to_js("web_components_local") {
+  js_files = [
+    "app.js",
+    "memory_card.js",
+    "page_thumbnail.js",
+  ]
+}
+
+group("web_components") {
+  public_deps = [ ":web_components_local" ]
+}
+
 grd_prefix = "memories"
 preprocess_folder = "preprocessed"
 preprocess_manifest = "preprocessed_manifest.json"
+preprocess_gen_manifest = "preprocessed_gen_manifest.json"
 
 preprocess_if_expr("preprocess") {
   defines = chrome_grit_defines + [ "is_official_build=$is_official_build" ]
@@ -40,9 +85,23 @@
   in_files = [
     "browser_proxy.js",
     "memories.js",
+    "utils.js",
   ]
 }
 
+preprocess_if_expr("preprocess_gen") {
+  defines = chrome_grit_defines + [ "is_official_build=$is_official_build" ]
+  in_folder = target_gen_dir
+  out_folder = "$target_gen_dir/$preprocess_folder"
+  out_manifest = "$target_gen_dir/$preprocess_gen_manifest"
+  in_files = [
+    "app.js",
+    "memory_card.js",
+    "page_thumbnail.js",
+  ]
+  deps = [ ":web_components" ]
+}
+
 generate_grd("build_memories_api_mojo_grdp") {
   grd_prefix = "$grd_prefix" + "_API"
   out_grd = "$target_gen_dir/memories_api_mojo_resources.grdp"
@@ -78,8 +137,12 @@
     ":build_memories_api_mojo_grdp",
     ":build_memories_definition_mojo_grdp",
     ":preprocess",
+    ":preprocess_gen",
   ]
-  manifest_files = [ "$target_gen_dir/$preprocess_manifest" ]
+  manifest_files = [
+    "$target_gen_dir/$preprocess_manifest",
+    "$target_gen_dir/$preprocess_gen_manifest",
+  ]
 }
 
 grit("resources") {
diff --git a/chrome/browser/resources/memories/app.html b/chrome/browser/resources/memories/app.html
new file mode 100644
index 0000000..d8bb9cb
--- /dev/null
+++ b/chrome/browser/resources/memories/app.html
@@ -0,0 +1,56 @@
+<style include="cr-shared-style">
+  :host {
+    --memory-card-width: 742px;
+  }
+
+  .container {
+    display: flex;
+    flex-direction: column;
+    margin: 40px auto 10px;
+    padding-inline-end: 20px;
+    padding-inline-start: 20px;
+    width: var(--memory-card-width);
+  }
+
+  .header {
+    align-items: center;
+    display: flex;
+  }
+
+  page-thumbnail {
+    --border-radius: 16px;
+    --max-height: 100px;
+    --max-width: 100px;
+    margin-inline-end: 25px;
+  }
+
+  .title {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .title .description {
+    color: var(--google-grey-refresh-700);
+    font-size: 12px;
+    line-height: 20px;
+  }
+
+  .title .text {
+    font-size: 36px;
+    line-height: 42px;
+  }
+</style>
+<div class="container">
+  <div class="header" hidden$="[[!title_]]">
+    <page-thumbnail page="[[createPageWithThumbnail_(result_.thumbnailUrl)]]"
+        hidden$="[[!result_.thumbnailUrl]]">
+    </page-thumbnail>
+    <div class="title">
+      <span class="description">$i18n{memoryTitleDescription}</span>
+      <span class="text">[[decodeMojoString16_(result_.title)]]</span>
+    </div>
+  </div>
+  <template is="dom-repeat" items="[[result_.memories]]" as="memory">
+    <memory-card memory="[[memory]]"></memory-card>
+  </template>
+</div>
diff --git a/chrome/browser/resources/memories/app.js b/chrome/browser/resources/memories/app.js
new file mode 100644
index 0000000..46565e0e
--- /dev/null
+++ b/chrome/browser/resources/memories/app.js
@@ -0,0 +1,102 @@
+// 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 './memory_card.js';
+import './page_thumbnail.js';
+import 'chrome://resources/cr_elements/shared_style_css.m.js';
+
+import {MemoriesResult, PageCallbackRouter, PageHandlerRemote} from '/chrome/browser/ui/webui/memories/memories.mojom-webui.js';
+import {String16} from 'chrome://resources/mojo/mojo/public/mojom/base/string16.mojom-webui.js';
+import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {BrowserProxy} from './browser_proxy.js';
+import {decodeMojoString16} from './utils.js';
+
+/**
+ * @fileoverview This file provides the root custom element for the Memories
+ * landing page.
+ */
+
+class MemoriesAppElement extends PolymerElement {
+  static get is() {
+    return 'memories-app';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+  static get properties() {
+    return {
+      //========================================================================
+      // Private properties
+      //========================================================================
+
+      /**
+       * The currently displayed Memories returned by the browser in response to
+       * a request for Memories related to a given query or those within a given
+       * timespan.
+       * @private {!MemoriesResult}
+       */
+      result_: Object,
+    };
+  }
+
+  constructor() {
+    super();
+    /** @private {PageHandlerRemote} */
+    this.pageHandler_ = BrowserProxy.getInstance().handler;
+    /** @private {!PageCallbackRouter} */
+    this.callbackRouter_ = BrowserProxy.getInstance().callbackRouter;
+  }
+
+  /** @override */
+  ready() {
+    super.ready();
+    // <if expr="not is_official_build">
+    this.onBrowserIdle_().then(() => {
+      const query = decodeURI(window.location.hash.substr(1));
+      this.pageHandler_.getSampleMemories(query).then(({result}) => {
+        this.result_ = result;
+      });
+    });
+    // </if>
+  }
+
+  //============================================================================
+  // Helper methods
+  //============================================================================
+
+  /**
+   * @param {Url} thumbnailUrl
+   * @return {!{thumbnailUrl: Url}} WebPage with the thumbnailUrl property only.
+   * @private
+   */
+  createPageWithThumbnail_(thumbnailUrl) {
+    return {thumbnailUrl};
+  }
+
+  /**
+   * Converts a Mojo String16 to a JS string.
+   * @param {String16} str
+   * @return {string}
+   * @private
+   */
+  decodeMojoString16_(str) {
+    return decodeMojoString16(str);
+  }
+
+  /**
+   * @return {!Promise} A promise that resolves when the browser is idle.
+   * @private
+   */
+  onBrowserIdle_() {
+    return new Promise((resolve) => {
+      window.requestIdleCallback(resolve);
+    });
+  }
+}
+
+customElements.define(MemoriesAppElement.is, MemoriesAppElement);
diff --git a/chrome/browser/resources/memories/memories.html b/chrome/browser/resources/memories/memories.html
index 35ab054e..4102032 100644
--- a/chrome/browser/resources/memories/memories.html
+++ b/chrome/browser/resources/memories/memories.html
@@ -8,12 +8,11 @@
   <style>
     html {
       background: var(--md-background-color);
-      overflow: hidden;
+      overflow-x: hidden;
+      overflow-y: scroll;
     }
 
-    html,
     body {
-      height: 100%;
       margin: 0;
     }
   </style>
diff --git a/chrome/browser/resources/memories/memories.js b/chrome/browser/resources/memories/memories.js
index b120c69..a42cf15 100644
--- a/chrome/browser/resources/memories/memories.js
+++ b/chrome/browser/resources/memories/memories.js
@@ -2,16 +2,4 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {MemoriesResult} from '/chrome/browser/ui/webui/memories/memories.mojom-webui.js';
-
-import {BrowserProxy} from './browser_proxy.js';
-
-// <if expr="not is_official_build">
-const query = decodeURI(window.location.hash.substr(1));
-if (query) {
-  BrowserProxy.getInstance().handler.getSampleMemories(query).then(
-      (/** @type {!MemoriesResult} */ result) => {
-        console.log(result);
-      });
-}
-// </if>
+import './app.js';
diff --git a/chrome/browser/resources/memories/memory_card.html b/chrome/browser/resources/memories/memory_card.html
new file mode 100644
index 0000000..844f85b
--- /dev/null
+++ b/chrome/browser/resources/memories/memory_card.html
@@ -0,0 +1,12 @@
+<style include="cr-shared-style">
+  :host {
+    background-color: #fff;
+    border: 1px solid var(--google-grey-refresh-300);
+    border-radius: 16px;
+    box-sizing: border-box;
+    color: var(--google-grey-900);
+    margin: 24px 0;
+    padding: 24px;
+    width: var(--memory-card-width);
+  }
+</style>
diff --git a/chrome/browser/resources/memories/memory_card.js b/chrome/browser/resources/memories/memory_card.js
new file mode 100644
index 0000000..e6b187b
--- /dev/null
+++ b/chrome/browser/resources/memories/memory_card.js
@@ -0,0 +1,38 @@
+// 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 'chrome://resources/cr_elements/shared_style_css.m.js';
+
+import {Memory} from '/components/memories/core/memories.mojom-webui.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+/**
+ * @fileoverview This file provides a custom element displaying a Memory.
+ */
+
+class MemoryCardElement extends PolymerElement {
+  static get is() {
+    return 'memory-card';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+  static get properties() {
+    return {
+      //========================================================================
+      // Public properties
+      //========================================================================
+
+      /**
+       * The Memory displayed by this element.
+       * @type {!Memory}
+       */
+      memory: Object,
+    };
+  }
+}
+
+customElements.define(MemoryCardElement.is, MemoryCardElement);
diff --git a/chrome/browser/resources/memories/page_thumbnail.html b/chrome/browser/resources/memories/page_thumbnail.html
new file mode 100644
index 0000000..956d456
--- /dev/null
+++ b/chrome/browser/resources/memories/page_thumbnail.html
@@ -0,0 +1,28 @@
+<style include="cr-shared-style">
+  :host {
+    align-items: center;
+    display: flex;
+    height: var(--max-height);
+    justify-content: center;
+    width: var(--max-width);
+  }
+
+  a {
+    position: relative;
+  }
+
+  :host(:not([is-clickable])) a {
+    cursor: default;
+    pointer-events: none;
+  }
+
+  img {
+    border-radius: var(--border-radius, 0);
+    max-height: var(--max-height);
+    max-width: var(--max-width);
+  }
+</style>
+<a href$="[[page.url.url]]">
+  <img title="[[decodeMojoString16_(page.title)]]"
+      src$="[[thumbnailSrc_(page.thumbnailUrl)]]">
+</a>
diff --git a/chrome/browser/resources/memories/page_thumbnail.js b/chrome/browser/resources/memories/page_thumbnail.js
new file mode 100644
index 0000000..4ff87de5
--- /dev/null
+++ b/chrome/browser/resources/memories/page_thumbnail.js
@@ -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.
+
+import 'chrome://resources/cr_elements/shared_style_css.m.js';
+
+import {WebPage} from '/components/memories/core/memories.mojom-webui.js';
+import {String16} from 'chrome://resources/mojo/mojo/public/mojom/base/string16.mojom-webui.js';
+import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {decodeMojoString16} from './utils.js';
+
+/**
+ * @fileoverview This file provides a custom element displaying a page
+ * thumbnail.
+ */
+
+class PageThumbnailElement extends PolymerElement {
+  static get is() {
+    return 'page-thumbnail';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+  static get properties() {
+    return {
+      //========================================================================
+      // Public properties
+      //========================================================================
+
+      /**
+       * The page for which the thumbnail is shown.
+       * @type {!WebPage}
+       */
+      page: Object,
+    };
+  }
+
+  //============================================================================
+  // Helper methods
+  //============================================================================
+
+  /**
+   * @param {Url} thumbnailUrl
+   * @return {string}
+   * @private
+   */
+  thumbnailSrc_(thumbnailUrl) {
+    return thumbnailUrl ? `chrome://image?${thumbnailUrl.url}` : '';
+  }
+
+  /**
+   * Converts a Mojo String16 to a JS string.
+   * @param {String16} str
+   * @return {string}
+   * @private
+   */
+  decodeMojoString16_(str) {
+    return decodeMojoString16(str);
+  }
+}
+
+customElements.define(PageThumbnailElement.is, PageThumbnailElement);
diff --git a/chrome/browser/resources/memories/utils.js b/chrome/browser/resources/memories/utils.js
new file mode 100644
index 0000000..08dc8cfc
--- /dev/null
+++ b/chrome/browser/resources/memories/utils.js
@@ -0,0 +1,19 @@
+// 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 {String16} from 'chrome://resources/mojo/mojo/public/mojom/base/string16.mojom-webui.js';
+
+/**
+ * @fileoverview This file provides shared utility functions used by the custom
+ * elements in the Memories landing page.
+ */
+
+/**
+ * Converts a Mojo String16 to a JS string.
+ * @param {?String16} str
+ * @return {string}
+ */
+export function decodeMojoString16(str) {
+  return str ? str.data.map(ch => String.fromCodePoint(ch)).join('') : '';
+}
diff --git a/chrome/browser/resources/settings/BUILD.gn b/chrome/browser/resources/settings/BUILD.gn
index bf50c533..6a612c2b 100644
--- a/chrome/browser/resources/settings/BUILD.gn
+++ b/chrome/browser/resources/settings/BUILD.gn
@@ -114,6 +114,8 @@
     "autofill_page/show_password_behavior.js",
     "chrome_cleanup_page/chrome_cleanup_proxy.js",
     "clear_browsing_data_dialog/clear_browsing_data_browser_proxy.js",
+    "controls/pref_control_behavior.js",
+    "controls/settings_boolean_control_behavior.js",
     "controls/settings_idle_load.js",
     "downloads_page/downloads_browser_proxy.js",
     "ensure_lazy_loaded.js",
@@ -207,18 +209,16 @@
     "clear_browsing_data_dialog/history_deletion_dialog.js",
     "clear_browsing_data_dialog/installed_app_checkbox.js",
     "clear_browsing_data_dialog/passwords_deletion_dialog.js",
-    "controls/controlled_button.m.js",
-    "controls/controlled_radio_button.m.js",
-    "controls/extension_controlled_indicator.m.js",
-    "controls/password_prompt_dialog.m.js",
-    "controls/settings_boolean_control_behavior.m.js",
+    "controls/controlled_button.js",
+    "controls/controlled_radio_button.js",
+    "controls/extension_controlled_indicator.js",
+    "controls/password_prompt_dialog.js",
     "controls/settings_checkbox.js",
-    "controls/settings_dropdown_menu.m.js",
-    "controls/pref_control_behavior.m.js",
-    "controls/settings_radio_group.m.js",
-    "controls/settings_slider.m.js",
-    "controls/settings_textarea.m.js",
-    "controls/settings_toggle_button.m.js",
+    "controls/settings_dropdown_menu.js",
+    "controls/settings_radio_group.js",
+    "controls/settings_slider.js",
+    "controls/settings_textarea.js",
+    "controls/settings_toggle_button.js",
     "downloads_page/downloads_page.js",
     "extension_control_browser_proxy.m.js",
     "global_scroll_target_behavior.m.js",
@@ -576,7 +576,7 @@
     "autofill_page:web_components",
     "basic_page:web_components",
     "clear_browsing_data_dialog:web_components",
-    "controls:polymer3_elements",
+    "controls:web_components",
     "downloads_page:web_components",
     "languages_page:polymer3_elements",
     "on_startup_page:web_components",
diff --git a/chrome/browser/resources/settings/a11y_page/BUILD.gn b/chrome/browser/resources/settings/a11y_page/BUILD.gn
index 169bcf9..450b14a 100644
--- a/chrome/browser/resources/settings/a11y_page/BUILD.gn
+++ b/chrome/browser/resources/settings/a11y_page/BUILD.gn
@@ -36,7 +36,7 @@
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/a11y_page/captions_subpage.m.js" ]
   deps = [
     "../appearance_page:fonts_browser_proxy",
-    "../controls:settings_dropdown_menu.m",
+    "../controls:settings_dropdown_menu",
     "../prefs:prefs_behavior.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:i18n_behavior.m",
@@ -48,7 +48,7 @@
 js_library("live_caption_section.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/a11y_page/live_caption_section.m.js" ]
   deps = [
-    "../controls:settings_toggle_button.m",
+    "../controls:settings_toggle_button",
     "../prefs:prefs_behavior.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:i18n_behavior.m",
diff --git a/chrome/browser/resources/settings/a11y_page/a11y_page.js b/chrome/browser/resources/settings/a11y_page/a11y_page.js
index b85809e..d20e34d 100644
--- a/chrome/browser/resources/settings/a11y_page/a11y_page.js
+++ b/chrome/browser/resources/settings/a11y_page/a11y_page.js
@@ -9,7 +9,7 @@
  * a subpage with lots of other settings on Chrome OS.
  */
 import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 import '../settings_page/settings_animated_pages.js';
 import '../settings_shared_css.m.js';
 
diff --git a/chrome/browser/resources/settings/appearance_page/BUILD.gn b/chrome/browser/resources/settings/appearance_page/BUILD.gn
index df73aa2..af570de 100644
--- a/chrome/browser/resources/settings/appearance_page/BUILD.gn
+++ b/chrome/browser/resources/settings/appearance_page/BUILD.gn
@@ -33,7 +33,7 @@
 js_library("appearance_fonts_page") {
   deps = [
     ":fonts_browser_proxy",
-    "../controls:settings_dropdown_menu.m",
+    "../controls:settings_dropdown_menu",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_slider:cr_slider.m",
     "//ui/webui/resources/js:i18n_behavior.m",
@@ -47,7 +47,7 @@
     "..:page_visibility",
     "..:route",
     "..:router.m",
-    "../controls:settings_dropdown_menu.m",
+    "../controls:settings_dropdown_menu",
     "../settings_page:settings_animated_pages",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:i18n_behavior.m",
@@ -62,7 +62,7 @@
 js_library("home_url_input") {
   deps = [
     ":appearance_browser_proxy",
-    "../controls:pref_control_behavior.m",
+    "../controls:pref_control_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/policy:cr_policy_indicator_behavior.m",
     "//ui/webui/resources/cr_elements/policy:cr_policy_pref_behavior.m",
diff --git a/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.js b/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.js
index ba3ea044..97359a5 100644
--- a/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.js
+++ b/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.js
@@ -4,7 +4,7 @@
 
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
-import '../controls/settings_slider.m.js';
+import '../controls/settings_slider.js';
 import '../settings_shared_css.m.js';
 
 import {SliderTick} from 'chrome://resources/cr_elements/cr_slider/cr_slider.m.js';
@@ -12,7 +12,7 @@
 import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {DropdownMenuOptionList} from '../controls/settings_dropdown_menu.m.js';
+import {DropdownMenuOptionList} from '../controls/settings_dropdown_menu.js';
 import {loadTimeData} from '../i18n_setup.js';
 
 import {FontsBrowserProxy, FontsBrowserProxyImpl, FontsData} from './fonts_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/appearance_page/appearance_page.js b/chrome/browser/resources/settings/appearance_page/appearance_page.js
index a07a96a..c9e4b14 100644
--- a/chrome/browser/resources/settings/appearance_page/appearance_page.js
+++ b/chrome/browser/resources/settings/appearance_page/appearance_page.js
@@ -6,10 +6,10 @@
 import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.m.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import 'chrome://resources/cr_elements/md_select_css.m.js';
-import '../controls/controlled_radio_button.m.js';
-import '../controls/extension_controlled_indicator.m.js';
-import '../controls/settings_radio_group.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/controlled_radio_button.js';
+import '../controls/extension_controlled_indicator.js';
+import '../controls/settings_radio_group.js';
+import '../controls/settings_toggle_button.js';
 import '../settings_page/settings_animated_pages.js';
 import '../settings_page/settings_subpage.js';
 import '../settings_shared_css.m.js';
@@ -20,7 +20,7 @@
 import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {DropdownMenuOptionList} from '../controls/settings_dropdown_menu.m.js';
+import {DropdownMenuOptionList} from '../controls/settings_dropdown_menu.js';
 import {loadTimeData} from '../i18n_setup.js';
 import {AppearancePageVisibility} from '../page_visibility.js';
 import {routes} from '../route.js';
diff --git a/chrome/browser/resources/settings/appearance_page/home_url_input.js b/chrome/browser/resources/settings/appearance_page/home_url_input.js
index 16d80aa6..5561534b 100644
--- a/chrome/browser/resources/settings/appearance_page/home_url_input.js
+++ b/chrome/browser/resources/settings/appearance_page/home_url_input.js
@@ -14,7 +14,7 @@
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {PrefControlBehavior} from '../controls/pref_control_behavior.m.js';
+import {PrefControlBehavior} from '../controls/pref_control_behavior.js';
 
 import {AppearanceBrowserProxy, AppearanceBrowserProxyImpl} from './appearance_browser_proxy.js';
 
diff --git a/chrome/browser/resources/settings/autofill_page/address_edit_dialog.js b/chrome/browser/resources/settings/autofill_page/address_edit_dialog.js
index 80658fa9..35e6eb8 100644
--- a/chrome/browser/resources/settings/autofill_page/address_edit_dialog.js
+++ b/chrome/browser/resources/settings/autofill_page/address_edit_dialog.js
@@ -14,7 +14,7 @@
 import 'chrome://resources/cr_elements/md_select_css.m.js';
 import '../settings_shared_css.m.js';
 import '../settings_vars_css.m.js';
-import '../controls/settings_textarea.m.js';
+import '../controls/settings_textarea.js';
 
 import {assertNotReached} from 'chrome://resources/js/assert.m.js';
 import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
diff --git a/chrome/browser/resources/settings/autofill_page/autofill_section.js b/chrome/browser/resources/settings/autofill_page/autofill_section.js
index ffbdcee..d7e58e36 100644
--- a/chrome/browser/resources/settings/autofill_page/autofill_section.js
+++ b/chrome/browser/resources/settings/autofill_page/autofill_section.js
@@ -13,8 +13,8 @@
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 import '../settings_shared_css.m.js';
-import '../controls/extension_controlled_indicator.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/extension_controlled_indicator.js';
+import '../controls/settings_toggle_button.js';
 import '../prefs/prefs.m.js';
 import './address_edit_dialog.js';
 import './address_remove_confirmation_dialog.js';
diff --git a/chrome/browser/resources/settings/autofill_page/password_check.js b/chrome/browser/resources/settings/autofill_page/password_check.js
index eb150c4..cc62ebd 100644
--- a/chrome/browser/resources/settings/autofill_page/password_check.js
+++ b/chrome/browser/resources/settings/autofill_page/password_check.js
@@ -17,26 +17,31 @@
 import './password_check_edit_disclaimer_dialog.js';
 import './password_check_list_item.js';
 import './password_remove_confirmation_dialog.js';
+// <if expr="chromeos">
+import '../controls/password_prompt_dialog.js';
+// </if>
 
 import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
 import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
 import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+// <if expr="chromeos">
+import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
+// </if>
 import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../i18n_setup.js';
 import {SyncBrowserProxyImpl, SyncPrefs, SyncStatus} from '../people_page/sync_browser_proxy.m.js';
 import {PrefsBehavior} from '../prefs/prefs_behavior.m.js';
-import {Route, Router, RouteObserverBehavior} from '../router.m.js';
 import {routes} from '../route.js';
+import {Route, RouteObserverBehavior, Router} from '../router.m.js';
 
-import {PasswordCheckBehavior} from './password_check_behavior.js';
-import {PasswordManagerImpl, PasswordManagerProxy} from './password_manager_proxy.js';
 // <if expr="chromeos">
-import '../controls/password_prompt_dialog.m.js';
-import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
 import {BlockingRequestManager} from './blocking_request_manager.js';
 // </if>
+import {PasswordCheckBehavior} from './password_check_behavior.js';
+import {PasswordManagerImpl, PasswordManagerProxy} from './password_manager_proxy.js';
+
 
 const CheckState = chrome.passwordsPrivate.PasswordCheckState;
 
diff --git a/chrome/browser/resources/settings/autofill_page/passwords_section.js b/chrome/browser/resources/settings/autofill_page/passwords_section.js
index 27bcb1a..91ad28d9 100644
--- a/chrome/browser/resources/settings/autofill_page/passwords_section.js
+++ b/chrome/browser/resources/settings/autofill_page/passwords_section.js
@@ -29,8 +29,8 @@
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 
-import '../controls/extension_controlled_indicator.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/extension_controlled_indicator.js';
+import '../controls/settings_toggle_button.js';
 import {GlobalScrollTargetBehavior} from '../global_scroll_target_behavior.m.js';
 import {loadTimeData} from '../i18n_setup.js';
 import {SyncBrowserProxyImpl, SyncPrefs, SyncStatus} from '../people_page/sync_browser_proxy.m.js';
@@ -51,7 +51,7 @@
 import './passwords_shared_css.js';
 import './avatar_icon.js';
 // <if expr="chromeos">
-import '../controls/password_prompt_dialog.m.js';
+import '../controls/password_prompt_dialog.js';
 import {BlockingRequestManager} from './blocking_request_manager.js';
 // </if>
 
diff --git a/chrome/browser/resources/settings/autofill_page/payments_section.js b/chrome/browser/resources/settings/autofill_page/payments_section.js
index 9280ca7..4a849c70 100644
--- a/chrome/browser/resources/settings/autofill_page/payments_section.js
+++ b/chrome/browser/resources/settings/autofill_page/payments_section.js
@@ -14,7 +14,7 @@
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 import '../settings_shared_css.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 import '../prefs/prefs.m.js';
 import './credit_card_edit_dialog.js';
 import './passwords_shared_css.js';
diff --git a/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_page.js b/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_page.js
index 5b42b1e..cd2cda1 100644
--- a/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_page.js
+++ b/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_page.js
@@ -11,7 +11,7 @@
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
 import 'chrome://resources/polymer/v3_0/paper-styles/color.js';
-import '../controls/controlled_button.m.js';
+import '../controls/controlled_button.js';
 import '../controls/settings_checkbox.js';
 import '../prefs/prefs.m.js';
 import '../settings_shared_css.m.js';
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index 05aa265..fc36db02 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -10,6 +10,7 @@
 import("//ui/webui/resources/tools/js_modulizer.gni")
 import("//ui/webui/webui_features.gni")
 import("../../tools/optimize_webui.gni")
+import("../settings.gni")
 import("./os_settings.gni")
 
 preprocess_folder_v3 = "preprocess_v3"
@@ -202,6 +203,8 @@
     "chromeos/ensure_lazy_loaded.m.js",
     "chromeos/lazy_load.js",
     "chromeos/os_settings.js",
+    "controls/pref_control_behavior.js",
+    "controls/settings_boolean_control_behavior.js",
     "i18n_setup.js",
     "page_visibility.js",
     "privacy_page/privacy_page_browser_proxy.js",
@@ -449,17 +452,15 @@
     "chromeos/pref_to_setting_metric_converter.m.js",
     "chromeos/route_origin_behavior.m.js",
     "chromeos/search_handler.m.js",
-    "controls/controlled_button.m.js",
-    "controls/controlled_radio_button.m.js",
-    "controls/extension_controlled_indicator.m.js",
-    "controls/password_prompt_dialog.m.js",
-    "controls/pref_control_behavior.m.js",
-    "controls/settings_boolean_control_behavior.m.js",
-    "controls/settings_dropdown_menu.m.js",
-    "controls/settings_radio_group.m.js",
-    "controls/settings_slider.m.js",
-    "controls/settings_textarea.m.js",
-    "controls/settings_toggle_button.m.js",
+    "controls/controlled_button.js",
+    "controls/controlled_radio_button.js",
+    "controls/extension_controlled_indicator.js",
+    "controls/password_prompt_dialog.js",
+    "controls/settings_dropdown_menu.js",
+    "controls/settings_radio_group.js",
+    "controls/settings_slider.js",
+    "controls/settings_textarea.js",
+    "controls/settings_toggle_button.js",
     "extension_control_browser_proxy.m.js",
     "global_scroll_target_behavior.m.js",
     "icons.m.js",
@@ -532,6 +533,7 @@
 
 js_type_check("closure_compile_local_module") {
   is_polymer3 = true
+  closure_flags = settings_closure_flags
   deps = [
     ":lazy_load",
     ":metrics_recorder.m",
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn
index b4a0a69..6c6ab1f9e 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn
@@ -8,6 +8,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":album_item.m",
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/crostini_page/BUILD.gn
index f298b84a..9f90608 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/BUILD.gn
@@ -166,7 +166,7 @@
     ":crostini_browser_proxy.m",
     ":crostini_port_forwarding_add_port_dialog.m",
     "..:metrics_recorder.m",
-    "../../controls:settings_toggle_button.m",
+    "../../controls:settings_toggle_button",
     "../../prefs:prefs_behavior.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu.m",
@@ -208,7 +208,7 @@
     "..:os_route.m",
     "..:route_origin_behavior.m",
     "../..:router.m",
-    "../../controls:settings_toggle_button.m",
+    "../../controls:settings_toggle_button",
     "../../prefs:prefs_behavior.m",
     "//ui/webui/resources/cr_elements/cr_button:cr_button.m",
     "//ui/webui/resources/cr_elements/cr_link_row:cr_link_row.m",
@@ -241,6 +241,7 @@
   html_file = "crostini_arc_adb.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -249,6 +250,7 @@
   html_file = "crostini_arc_adb_confirmation_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -257,6 +259,7 @@
   html_file = "crostini_disk_resize_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -265,6 +268,7 @@
   html_file = "crostini_disk_resize_confirmation_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -273,6 +277,7 @@
   html_file = "crostini_export_import.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -281,6 +286,7 @@
   html_file = "crostini_import_confirmation_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -289,6 +295,7 @@
   html_file = "crostini_mic_sharing_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -296,8 +303,8 @@
   js_file = "crostini_page.js"
   html_file = "crostini_page.html"
   html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -306,6 +313,7 @@
   html_file = "crostini_port_forwarding.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -314,6 +322,7 @@
   html_file = "crostini_port_forwarding_add_port_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -322,6 +331,7 @@
   html_file = "crostini_subpage.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/date_time_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/date_time_page/BUILD.gn
index 5349f5f..b0c18b157 100644
--- a/chrome/browser/resources/settings/chromeos/date_time_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/date_time_page/BUILD.gn
@@ -8,6 +8,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":date_time_page.m",
@@ -55,7 +56,7 @@
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/date_time_page/timezone_selector.m.js" ]
   deps = [
     ":timezone_browser_proxy.m",
-    "../../controls:settings_dropdown_menu.m",
+    "../../controls:settings_dropdown_menu",
     "../../prefs:prefs_behavior.m",
     "../../prefs:prefs_types.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
diff --git a/chrome/browser/resources/settings/chromeos/device_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/device_page/BUILD.gn
index c592e6dd..4bd71408 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/device_page/BUILD.gn
@@ -9,6 +9,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":device_page.m",
@@ -55,7 +56,7 @@
   deps = [
     "..:os_route.m",
     "../..:router.m",
-    "../../controls:settings_slider.m",
+    "../../controls:settings_slider",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_slider:cr_slider.m",
     "//ui/webui/resources/js:cr.m",
@@ -102,7 +103,7 @@
     "..:os_route.m",
     "../..:i18n_setup",
     "../..:router.m",
-    "../../controls:settings_dropdown_menu.m",
+    "../../controls:settings_dropdown_menu",
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
@@ -139,7 +140,7 @@
     "..:deep_linking_behavior.m",
     "..:os_route.m",
     "../..:router.m",
-    "../../controls:settings_toggle_button.m",
+    "../../controls:settings_toggle_button",
     "../localized_link:localized_link.m",
   ]
   extra_deps = [ ":pointers_module" ]
diff --git a/chrome/browser/resources/settings/chromeos/google_assistant_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/google_assistant_page/BUILD.gn
index d6a287c..eb0b8da8 100644
--- a/chrome/browser/resources/settings/chromeos/google_assistant_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/google_assistant_page/BUILD.gn
@@ -6,8 +6,10 @@
 import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/polymer/polymer.gni")
 import("//ui/webui/resources/tools/js_modulizer.gni")
+import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":google_assistant_browser_proxy.m",
@@ -31,8 +33,8 @@
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior.m",
     "//chrome/browser/resources/settings/chromeos:metrics_recorder.m",
     "//chrome/browser/resources/settings/chromeos:os_route.m",
-    "//chrome/browser/resources/settings/controls:controlled_button.m",
-    "//chrome/browser/resources/settings/controls:settings_toggle_button.m",
+    "//chrome/browser/resources/settings/controls:controlled_button",
+    "//chrome/browser/resources/settings/controls:settings_toggle_button",
     "//chrome/browser/resources/settings/prefs:pref_util.m",
     "//chrome/browser/resources/settings/prefs:prefs.m",
     "//chrome/browser/resources/settings/prefs:prefs_behavior.m",
@@ -59,6 +61,7 @@
   html_file = "google_assistant_page.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/guest_os/BUILD.gn b/chrome/browser/resources/settings/chromeos/guest_os/BUILD.gn
index 034e35e3..3c255b35 100644
--- a/chrome/browser/resources/settings/chromeos/guest_os/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/guest_os/BUILD.gn
@@ -8,6 +8,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":guest_os_browser_proxy.m",
@@ -62,6 +63,7 @@
   html_file = "guest_os_shared_paths.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -70,6 +72,7 @@
   html_file = "guest_os_shared_usb_devices.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
index 1ae06c9..b4aee676 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
@@ -6,6 +6,7 @@
 import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/polymer/polymer.gni")
 import("//ui/webui/resources/tools/js_modulizer.gni")
+import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
   closure_flags = os_settings_closure_flags
@@ -85,8 +86,8 @@
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior.m",
     "//chrome/browser/resources/settings/chromeos:metrics_recorder.m",
     "//chrome/browser/resources/settings/chromeos/os_people_page:os_sync_browser_proxy.m",
-    "//chrome/browser/resources/settings/controls:controlled_button.m",
-    "//chrome/browser/resources/settings/controls:settings_toggle_button.m",
+    "//chrome/browser/resources/settings/controls:controlled_button",
+    "//chrome/browser/resources/settings/controls:settings_toggle_button",
     "//chrome/browser/resources/settings/people_page:sync_browser_proxy.m",
     "//chrome/browser/resources/settings/prefs:prefs.m",
     "//third_party/polymer/v3_0/components-chromium/iron-collapse:iron-collapse",
@@ -227,8 +228,8 @@
     ":internet_shared_css.m",
     "//chrome/browser/resources/settings:router.m",
     "//chrome/browser/resources/settings/chromeos:os_route.m",
-    "//chrome/browser/resources/settings/controls:extension_controlled_indicator.m",
-    "//chrome/browser/resources/settings/controls:settings_toggle_button.m",
+    "//chrome/browser/resources/settings/controls:extension_controlled_indicator",
+    "//chrome/browser/resources/settings/controls:settings_toggle_button",
     "//chrome/browser/resources/settings/prefs:prefs_behavior.m",
     "//third_party/polymer/v3_0/components-chromium/iron-flex-layout:iron-flex-layout-classes",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -370,6 +371,7 @@
   html_file = "internet_config.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -378,6 +380,7 @@
   html_file = "internet_detail_menu.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -386,6 +389,7 @@
   html_file = "internet_detail_page.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports + [ "chrome/browser/resources/settings/chromeos/internet_page/internet_page_browser_proxy.html|InternetPageBrowserProxy,InternetPageBrowserProxyImpl" ]
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -394,6 +398,7 @@
   html_file = "internet_known_networks_page.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -401,8 +406,8 @@
   js_file = "internet_page.js"
   html_file = "internet_page.html"
   html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -417,6 +422,7 @@
   html_file = "internet_subpage.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -425,6 +431,7 @@
   html_file = "network_proxy_section.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -433,6 +440,7 @@
   html_file = "network_summary.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -441,6 +449,7 @@
   html_file = "network_summary_item.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -449,6 +458,7 @@
   html_file = "tether_connection_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites =
       [ "cros_network_config.mojom.m.js|cros_network_config.mojom-lite.js" ]
 }
@@ -458,6 +468,7 @@
   html_file = "cellular_setup_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -466,6 +477,7 @@
   html_file = "cellular_networks_list.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -474,6 +486,7 @@
   html_file = "esim_install_error_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -482,6 +495,7 @@
   html_file = "esim_rename_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -490,6 +504,7 @@
   html_file = "esim_remove_profile_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
index fb83368..f3696cd 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
@@ -1940,9 +1940,7 @@
         if (this.isUpdatedCellularUiEnabled_) {
           fields.push('cellular.activationState');
         }
-        fields.push(
-            'cellular.family', 'cellular.networkTechnology',
-            'cellular.servingOperator.code');
+        fields.push('cellular.networkTechnology');
         break;
       case chromeos.networkConfig.mojom.NetworkType.kWiFi:
         fields.push(
@@ -1968,10 +1966,8 @@
 
     return [
       'cellular.homeProvider.name', 'cellular.homeProvider.country',
-      'cellular.homeProvider.code', 'cellular.manufacturer', 'cellular.modelId',
       'cellular.firmwareRevision', 'cellular.hardwareRevision', 'cellular.esn',
-      'cellular.iccid', 'cellular.imei', 'cellular.imsi', 'cellular.mdn',
-      'cellular.meid', 'cellular.min'
+      'cellular.iccid', 'cellular.imei', 'cellular.meid', 'cellular.min'
     ];
   },
 
diff --git a/chrome/browser/resources/settings/chromeos/kerberos_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/kerberos_page/BUILD.gn
index ef7cbf1..32bb4de 100644
--- a/chrome/browser/resources/settings/chromeos/kerberos_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/kerberos_page/BUILD.gn
@@ -51,7 +51,7 @@
   deps = [
     ":kerberos_accounts_browser_proxy.m",
     "..:metrics_recorder.m",
-    "//chrome/browser/resources/settings/controls:settings_textarea.m",
+    "//chrome/browser/resources/settings/controls:settings_textarea",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m",
     "//ui/webui/resources/cr_elements/cr_input:cr_input.m",
diff --git a/chrome/browser/resources/settings/chromeos/localized_link/BUILD.gn b/chrome/browser/resources/settings/chromeos/localized_link/BUILD.gn
index 44b1ac2..59693f3b 100644
--- a/chrome/browser/resources/settings/chromeos/localized_link/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/localized_link/BUILD.gn
@@ -30,6 +30,7 @@
   html_file = "localized_link.html"
   html_type = "dom-module"
   namespace_rewrites = os_settings_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
   auto_imports =
       os_settings_auto_imports +
       [ "ui/webui/resources/html/assert.html|assert,assertNotReached" ]
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
index 07a6db99..d5292c02 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
@@ -8,6 +8,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":multidevice_browser_proxy.m",
@@ -89,7 +90,7 @@
     "..:metrics_recorder.m",
     "..:os_route.m",
     "../..:router.m",
-    "../../controls:password_prompt_dialog.m",
+    "../../controls:password_prompt_dialog",
     "../../prefs:prefs_behavior.m",
     "../localized_link:localized_link.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
diff --git a/chrome/browser/resources/settings/chromeos/nearby_share_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/nearby_share_page/BUILD.gn
index a50e06a..a471172 100644
--- a/chrome/browser/resources/settings/chromeos/nearby_share_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/nearby_share_page/BUILD.gn
@@ -179,7 +179,7 @@
     ":nearby_share_high_visibility_page_module",
     ":nearby_share_receive_dialog_module",
     ":nearby_share_subpage_module",
-    "../../controls:polymer3_elements",
+    "../../controls:web_components",
     "../../prefs:polymer3_elements",
     "//chrome/browser/ui/webui/nearby_share:mojom_js",
   ]
@@ -205,6 +205,7 @@
   html_file = "nearby_share_confirm_page.html"
   html_type = "dom-module"
   namespace_rewrites = os_settings_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
   auto_imports = os_settings_auto_imports
 }
 
@@ -214,6 +215,7 @@
   html_type = "dom-module"
   namespace_rewrites =
       os_settings_namespace_rewrites + nearby_shared_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
   auto_imports =
       os_settings_auto_imports + nearby_shared_auto_imports_closure_fix
 }
@@ -223,6 +225,7 @@
   html_file = "nearby_share_data_usage_dialog.html"
   html_type = "dom-module"
   namespace_rewrites = os_settings_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
   auto_imports = os_settings_auto_imports
 }
 
@@ -232,6 +235,7 @@
   html_type = "dom-module"
   namespace_rewrites =
       os_settings_namespace_rewrites + nearby_shared_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
   auto_imports =
       os_settings_auto_imports + nearby_shared_auto_imports_closure_fix
 }
@@ -241,6 +245,7 @@
   html_file = "nearby_share_high_visibility_page.html"
   html_type = "dom-module"
   namespace_rewrites = os_settings_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
   auto_imports = os_settings_auto_imports
 }
 
@@ -261,6 +266,7 @@
   html_type = "dom-module"
   namespace_rewrites =
       os_settings_namespace_rewrites + nearby_shared_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
   auto_imports =
       os_settings_auto_imports + nearby_shared_auto_imports_closure_fix +
       [ "chrome/browser/resources/settings/chromeos/os_route.html|routes" ]
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_a11y_page/BUILD.gn
index 5719ce0..c2a8ce2 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/BUILD.gn
@@ -6,6 +6,7 @@
 import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/polymer/polymer.gni")
 import("//ui/webui/resources/tools/js_modulizer.gni")
+import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
   closure_flags = os_settings_closure_flags
@@ -40,8 +41,8 @@
     "//chrome/browser/resources/settings/chromeos:os_route.m",
     "//chrome/browser/resources/settings/chromeos:route_origin_behavior.m",
     "//chrome/browser/resources/settings/chromeos/device_page:device_page_browser_proxy.m",
-    "//chrome/browser/resources/settings/controls:settings_slider.m",
-    "//chrome/browser/resources/settings/controls:settings_toggle_button.m",
+    "//chrome/browser/resources/settings/controls:settings_slider",
+    "//chrome/browser/resources/settings/controls:settings_toggle_button",
     "//ui/webui/resources/js:load_time_data.m",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
@@ -148,7 +149,7 @@
     "//chrome/browser/resources/settings:router.m",
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior.m",
     "//chrome/browser/resources/settings/chromeos:os_route.m",
-    "//chrome/browser/resources/settings/controls:settings_slider.m",
+    "//chrome/browser/resources/settings/controls:settings_slider",
     "//chrome/browser/resources/settings/languages_page:languages_browser_proxy.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_button:cr_button.m",
@@ -185,6 +186,7 @@
   html_file = "manage_a11y_page.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -202,6 +204,7 @@
   html_file = "switch_access_action_assignment_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -210,6 +213,7 @@
   html_file = "switch_access_setup_guide_dialog.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -218,6 +222,7 @@
   html_file = "switch_access_subpage.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
@@ -226,6 +231,7 @@
   html_file = "tts_subpage.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_files_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_files_page/BUILD.gn
index 09fa862..f8d246e 100644
--- a/chrome/browser/resources/settings/chromeos/os_files_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_files_page/BUILD.gn
@@ -47,8 +47,8 @@
   js_file = "os_files_page.js"
   html_file = "os_files_page.html"
   html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
   auto_imports = os_settings_auto_imports
 }
 
@@ -57,5 +57,6 @@
   html_file = "smb_shares_page.html"
   html_type = "dom-module"
   namespace_rewrites = os_settings_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
   auto_imports = os_settings_auto_imports
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn
index f6fc195..afbef8cc 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn
@@ -85,13 +85,14 @@
     "..:deep_linking_behavior.m",
     "..:os_route.m",
     "../..:router.m",
-    "../../controls:settings_dropdown_menu.m",
-    "../../controls:settings_toggle_button.m",
+    "../../controls:settings_dropdown_menu",
+    "../../controls:settings_toggle_button",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_components/chromeos/quick_unlock:lock_screen_constants.m",
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:i18n_behavior.m",
     "//ui/webui/resources/js:load_time_data.m",
+    "//ui/webui/resources/js:plural_string_proxy",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
     "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
@@ -102,7 +103,7 @@
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen_password_prompt_dialog.m.js" ]
   deps = [
     ":lock_state_behavior.m",
-    "../../controls:password_prompt_dialog.m",
+    "../../controls:password_prompt_dialog",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_components/chromeos/quick_unlock:lock_screen_constants.m",
   ]
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.html b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.html
index b3d01777..670eaf5 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.html
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.html
@@ -9,6 +9,7 @@
 <link rel="import" href="chrome://resources/html/assert.html">
 <link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
+<link rel="import" href="chrome://resources/html/plural_string_proxy.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="../../controls/settings_toggle_button.html">
 <link rel="import" href="fingerprint_browser_proxy.html">
@@ -173,7 +174,7 @@
           <div class="start">
             $i18n{lockScreenEditFingerprints}
             <div class="secondary" id="lockScreenEditFingerprintsSecondary">
-              [[getDescriptionText_(numFingerprints_)]]
+              [[numFingerprintDescription_]]
             </div>
           </div>
           <div class="separator"></div>
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
index 688e73a..28e29218 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
@@ -102,6 +102,12 @@
     numFingerprints_: {
       type: Number,
       value: 0,
+      observer: 'updateNumFingerprintsDescription_',
+    },
+
+    /** @private */
+    numFingerprintsDescription_: {
+      type: String,
     },
 
     /**
@@ -338,13 +344,16 @@
   },
 
   /** @private */
-  getDescriptionText_() {
-    if (this.numFingerprints_ > 0) {
-      return this.i18n(
-          'lockScreenNumberFingerprints', this.numFingerprints_.toString());
+  updateNumFingerprintsDescription_() {
+    if (this.numFingerprints_ === 0) {
+      this.numFingerprintDescription_ =
+          this.i18n('lockScreenEditFingerprintsDescription');
+    } else {
+      PluralStringProxyImpl.getInstance()
+          .getPluralString(
+              'lockScreenNumberFingerprints', this.numFingerprints_)
+          .then(string => this.numFingerprintDescription_ = string);
     }
-
-    return this.i18n('lockScreenEditFingerprintsDescription');
   },
 
   /** @private */
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_printing_page/BUILD.gn
index 218e130..42a37ae 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/BUILD.gn
@@ -8,6 +8,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":cups_add_print_server_dialog.m",
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_privacy_page/BUILD.gn
index 8edf9cc3..add6cffb 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/BUILD.gn
@@ -63,8 +63,8 @@
   js_file = "os_privacy_page.js"
   html_file = "os_privacy_page.html"
   html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
   auto_imports = os_settings_auto_imports
 }
 
@@ -73,6 +73,7 @@
   html_file = "peripheral_data_access_protection_dialog.html"
   html_type = "dom-module"
   namespace_rewrites = os_settings_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
   auto_imports = os_settings_auto_imports
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.html b/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.html
index 62fbf05..f3ee513 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.html
@@ -39,19 +39,21 @@
 
       <div route-path="default">
         <template is="dom-if" if="[[isAccountManagementFlowsV2Enabled_]]">
-          <cr-link-row id="lockScreenSubpageTrigger"
-              on-click="onConfigureLockTap_"
-              label="[[selectLockScreenTitleString_(hasPinLogin)]]"
-              sub-label="[[getPasswordState_(hasPin,
-                  prefs.settings.enable_screen_lock.value)]]"
-              role-description="$i18n{subpageArrowRoleDescription}">
-          </cr-link-row>
-          <cr-link-row id="manageOtherPeopleSubpageTrigger" class="hr"
-              label="$i18n{manageOtherPeople}"
-              on-click="onManageOtherPeople_"
-              role-description="$i18n{subpageArrowRoleDescription}">
-          </cr-link-row>
-          <div class="hr"></div>
+          <template is="dom-if" if="[[!isGuestMode_]]" restamp>
+            <cr-link-row id="lockScreenSubpageTrigger"
+                on-click="onConfigureLockTap_"
+                label="[[selectLockScreenTitleString_(hasPinLogin)]]"
+                sub-label="[[getPasswordState_(hasPin,
+                    prefs.settings.enable_screen_lock.value)]]"
+                role-description="$i18n{subpageArrowRoleDescription}">
+            </cr-link-row>
+            <cr-link-row id="manageOtherPeopleSubpageTrigger" class="hr"
+                label="$i18n{manageOtherPeople}"
+                on-click="onManageOtherPeople_"
+                role-description="$i18n{subpageArrowRoleDescription}">
+            </cr-link-row>
+            <div class="hr"></div>
+          </template>
         </template>
 
 <if expr="_google_chrome">
@@ -108,36 +110,38 @@
       </div>
 
       <template is="dom-if" if="[[isAccountManagementFlowsV2Enabled_]]">
-        <template is="dom-if" route-path="/osPrivacy/lockScreen">
-          <settings-subpage
-              page-title="[[selectLockScreenTitleString_(hasPinLogin)]]"
-              associated-control="[[$$('#lockScreenSubpageTrigger')]]">
-            <settings-lock-screen id="lockScreen" set-modes="[[setModes_]]"
-                prefs="{{prefs}}" auth-token="[[authToken_]]"
-                on-invalidate-auth-token-requested="onInvalidateTokenRequested_"
-                on-password-requested="onPasswordRequested_">
-            </settings-lock-screen>
-          </settings-subpage>
-        </template>
-        <template is="dom-if" if="[[fingerprintUnlockEnabled_]]">
-          <template is="dom-if" route-path="/osPrivacy/lockScreen/fingerprint"
-              no-search>
-            <settings-subpage page-title="$i18n{lockScreenFingerprintTitle}">
-              <settings-fingerprint-list id="fingerprint-list"
-                  auth-token="[[authToken_.token]]"
-                  set-modes="[[setModes_]]"
+        <template is="dom-if" if="[[!isGuestMode_]]" restamp>
+          <template is="dom-if" route-path="/osPrivacy/lockScreen">
+            <settings-subpage
+                page-title="[[selectLockScreenTitleString_(hasPinLogin)]]"
+                associated-control="[[$$('#lockScreenSubpageTrigger')]]">
+              <settings-lock-screen id="lockScreen" set-modes="[[setModes_]]"
+                  prefs="{{prefs}}" auth-token="[[authToken_]]"
+                  on-invalidate-auth-token-requested="onInvalidateTokenRequested_"
                   on-password-requested="onPasswordRequested_">
-              </settings-fingerprint-list>
+              </settings-lock-screen>
             </settings-subpage>
           </template>
-        </template>
-        <template is="dom-if" route-path="/osPrivacy/accounts">
-          <settings-subpage
-              associated-control="[[$$('#manageOtherPeopleSubpageTrigger')]]"
-              page-title="$i18n{manageOtherPeople}">
-            <settings-users-page prefs="{{prefs}}">
-            </settings-users-page>
-          </settings-subpage>
+          <template is="dom-if" if="[[fingerprintUnlockEnabled_]]">
+            <template is="dom-if" route-path="/osPrivacy/lockScreen/fingerprint"
+                no-search>
+              <settings-subpage page-title="$i18n{lockScreenFingerprintTitle}">
+                <settings-fingerprint-list id="fingerprint-list"
+                    auth-token="[[authToken_.token]]"
+                    set-modes="[[setModes_]]"
+                    on-password-requested="onPasswordRequested_">
+                </settings-fingerprint-list>
+              </settings-subpage>
+            </template>
+          </template>
+          <template is="dom-if" route-path="/osPrivacy/accounts">
+            <settings-subpage
+                associated-control="[[$$('#manageOtherPeopleSubpageTrigger')]]"
+                page-title="$i18n{manageOtherPeople}">
+              <settings-users-page prefs="{{prefs}}">
+              </settings-users-page>
+            </settings-subpage>
+          </template>
         </template>
       </template>
 
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.js b/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.js
index 31ef37e9..d5634b3 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.js
@@ -118,6 +118,18 @@
       readOnly: true,
     },
 
+    /**
+     * Whether the user is in guest mode.
+     * @private {boolean}
+     */
+    isGuestMode_: {
+      type: Boolean,
+      value() {
+        return loadTimeData.getBoolean('isGuest');
+      },
+      readOnly: true,
+    },
+
     /** @private */
     showDisableProtectionDialog_: {
       type: Boolean,
diff --git a/chrome/browser/resources/settings/chromeos/os_reset_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_reset_page/BUILD.gn
index 618d4db0..5a713e45 100644
--- a/chrome/browser/resources/settings/chromeos/os_reset_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_reset_page/BUILD.gn
@@ -8,6 +8,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":os_powerwash_dialog.m",
@@ -63,6 +64,7 @@
   html_file = "os_powerwash_dialog.html"
   html_type = "dom-module"
   namespace_rewrites = os_settings_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
   auto_imports = os_settings_auto_imports
 }
 
@@ -70,7 +72,7 @@
   js_file = "os_reset_page.js"
   html_file = "os_reset_page.html"
   html_type = "dom-module"
-  migrated_imports = settings_migrated_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
   auto_imports = os_settings_auto_imports +
                  [ "ui/webui/resources/html/assert.html|assert" ]
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 0dbae35..4d578c5 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/BUILD.gn
@@ -5,6 +5,7 @@
 import("//chrome/browser/resources/settings/chromeos/os_settings.gni")
 import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/polymer/polymer.gni")
+import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
   closure_flags = os_settings_closure_flags
@@ -22,7 +23,7 @@
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior.m",
     "//chrome/browser/resources/settings/chromeos:os_route.m",
     "//chrome/browser/resources/settings/chromeos/google_assistant_page:google_assistant_page.m",
-    "//chrome/browser/resources/settings/controls:extension_controlled_indicator.m",
+    "//chrome/browser/resources/settings/controls:extension_controlled_indicator",
     "//chrome/browser/resources/settings/search_engines_page:search_engines_browser_proxy",
     "//chrome/browser/resources/settings/settings_page:settings_animated_pages",
     "//chrome/browser/resources/settings/settings_page:settings_subpage",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index 586e6f0..7aa69f8e 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -223,6 +223,7 @@
                              "ui/webui/resources/html/icon.html|getImage",
                              "ui/webui/resources/html/list_property_update_behavior.html|ListPropertyUpdateBehavior",
                              "ui/webui/resources/html/parse_html_subset.html|parseHtmlSubset",
+                             "ui/webui/resources/html/plural_string_proxy.html|PluralStringProxyImpl",
                              "ui/webui/resources/html/polymer.html|afterNextRender,Polymer,html,flush,Templatizer,TemplateInstanceBase",
                              "ui/webui/resources/html/util.html|HTMLEscape,listenOnce",
                              "ui/webui/resources/html/web_ui_listener_behavior.html|WebUIListenerBehavior",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn
index 166e605..70cf1b22 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn
@@ -6,6 +6,7 @@
 import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/polymer/polymer.gni")
 import("//ui/webui/resources/tools/js_modulizer.gni")
+import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
   closure_flags = os_settings_closure_flags
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_menu/BUILD.gn
index 71c10d6..3abe6ce 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/BUILD.gn
@@ -5,8 +5,10 @@
 import("//chrome/browser/resources/settings/chromeos/os_settings.gni")
 import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/polymer/polymer.gni")
+import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [ ":os_settings_menu.m" ]
 }
@@ -31,5 +33,6 @@
   html_file = "os_settings_menu.html"
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
+  migrated_imports = os_settings_migrated_imports
   namespace_rewrites = os_settings_namespace_rewrites
 }
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 1b131da5..d090916e 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
@@ -83,13 +83,13 @@
   js_file = "os_settings_page.js"
   html_file = "os_settings_page.html"
   html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
   auto_imports =
       os_settings_auto_imports + [
         "ui/webui/resources/html/polymer.html|Polymer,html,beforeNextRender",
         "ui/webui/resources/html/assert.html|assert",
       ]
   namespace_rewrites = os_settings_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
 }
 
 polymer_modulizer("settings_idle_load") {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn
index 4a76273a..86fcdd9 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn
@@ -7,6 +7,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":os_search_result_row.m",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_ui/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_ui/BUILD.gn
index 04e8b97..16c8f19 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_ui/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_ui/BUILD.gn
@@ -5,6 +5,7 @@
 import("//chrome/browser/resources/settings/chromeos/os_settings.gni")
 import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/polymer/polymer.gni")
+import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
   closure_flags = os_settings_closure_flags
diff --git a/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
index 072b6c1a1..d8e7deb 100644
--- a/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
@@ -7,6 +7,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [ ":os_toolbar.m" ]
 }
@@ -33,4 +34,5 @@
   html_type = "dom-module"
   auto_imports = os_settings_auto_imports
   namespace_rewrites = os_settings_namespace_rewrites
+  migrated_imports = os_settings_migrated_imports
 }
diff --git a/chrome/browser/resources/settings/chromeos/parental_controls_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/parental_controls_page/BUILD.gn
index 1b66dbf..2f8551dd 100644
--- a/chrome/browser/resources/settings/chromeos/parental_controls_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/parental_controls_page/BUILD.gn
@@ -8,6 +8,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":parental_controls_browser_proxy.m",
diff --git a/chrome/browser/resources/settings/clear_browsing_data_dialog/BUILD.gn b/chrome/browser/resources/settings/clear_browsing_data_dialog/BUILD.gn
index a7d9d965..029f53e 100644
--- a/chrome/browser/resources/settings/clear_browsing_data_dialog/BUILD.gn
+++ b/chrome/browser/resources/settings/clear_browsing_data_dialog/BUILD.gn
@@ -28,7 +28,7 @@
     "..:route",
     "..:router.m",
     "../controls:settings_checkbox",
-    "../controls:settings_dropdown_menu.m",
+    "../controls:settings_dropdown_menu",
     "../people_page:sync_browser_proxy.m",
     "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer",
     "//ui/webui/resources/js:load_time_data.m",
diff --git a/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.js b/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.js
index 882f756..b92d7a0 100644
--- a/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.js
+++ b/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.js
@@ -25,7 +25,7 @@
 import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {DropdownMenuOptionList} from '../controls/settings_dropdown_menu.m.js';
+import {DropdownMenuOptionList} from '../controls/settings_dropdown_menu.js';
 import {loadTimeData} from '../i18n_setup.js';
 import {StatusAction, SyncBrowserProxy, SyncBrowserProxyImpl, SyncStatus} from '../people_page/sync_browser_proxy.m.js';
 import {routes} from '../route.js';
diff --git a/chrome/browser/resources/settings/controls/BUILD.gn b/chrome/browser/resources/settings/controls/BUILD.gn
index c1c61fe2..e31fbc8 100644
--- a/chrome/browser/resources/settings/controls/BUILD.gn
+++ b/chrome/browser/resources/settings/controls/BUILD.gn
@@ -12,36 +12,33 @@
   is_polymer3 = true
   closure_flags = settings_closure_flags
   deps = [
-    ":controlled_button.m",
-    ":controlled_radio_button.m",
-    ":extension_controlled_indicator.m",
-    ":password_prompt_dialog.m",
-    ":pref_control_behavior.m",
-    ":settings_boolean_control_behavior.m",
+    ":controlled_button",
+    ":controlled_radio_button",
+    ":extension_controlled_indicator",
+    ":password_prompt_dialog",
+    ":pref_control_behavior",
+    ":settings_boolean_control_behavior",
     ":settings_checkbox",
-    ":settings_dropdown_menu.m",
+    ":settings_dropdown_menu",
     ":settings_idle_load",
-    ":settings_radio_group.m",
-    ":settings_slider.m",
-    ":settings_textarea.m",
-    ":settings_toggle_button.m",
+    ":settings_radio_group",
+    ":settings_slider",
+    ":settings_textarea",
+    ":settings_toggle_button",
   ]
 }
 
-js_library("controlled_button.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/controls/controlled_button.m.js" ]
+js_library("controlled_button") {
   deps = [
-    ":pref_control_behavior.m",
+    ":pref_control_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/policy:cr_policy_pref_behavior.m",
   ]
-  extra_deps = [ ":controlled_button_module" ]
 }
 
-js_library("controlled_radio_button.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/controls/controlled_radio_button.m.js" ]
+js_library("controlled_radio_button") {
   deps = [
-    ":pref_control_behavior.m",
+    ":pref_control_behavior",
     "../prefs:pref_util.m",
     "//third_party/polymer/v3_0/components-chromium/iron-a11y-keys-behavior:iron-a11y-keys-behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -49,22 +46,18 @@
     "//ui/webui/resources/js:assert.m",
   ]
   externs_list = [ "$externs_path/settings_private.js" ]
-  extra_deps = [ ":controlled_radio_button_module" ]
 }
 
-js_library("extension_controlled_indicator.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/controls/extension_controlled_indicator.m.js" ]
+js_library("extension_controlled_indicator") {
   deps = [
     "..:extension_control_browser_proxy.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
-  extra_deps = [ ":extension_controlled_indicator_module" ]
 }
 
-js_library("password_prompt_dialog.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/controls/password_prompt_dialog.m.js" ]
+js_library("password_prompt_dialog") {
   deps = [
     "..:router.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -73,37 +66,31 @@
   ]
   externs_list = chrome_extension_public_externs +
                  [ "$externs_path/quick_unlock_private.js" ]
-  extra_deps = [ ":password_prompt_dialog_module" ]
 }
 
-js_library("pref_control_behavior.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/controls/pref_control_behavior.m.js" ]
+js_library("pref_control_behavior") {
   deps = [ "../prefs:prefs_types.m" ]
-  extra_deps = [ ":modulize" ]
 }
 
-js_library("settings_boolean_control_behavior.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.m.js" ]
+js_library("settings_boolean_control_behavior") {
   deps = [
-    ":pref_control_behavior.m",
+    ":pref_control_behavior",
     "../prefs:prefs_types.m",
     "//ui/webui/resources/cr_elements/policy:cr_policy_pref_behavior.m",
     "//ui/webui/resources/js:assert.m",
   ]
-  extra_deps = [ ":modulize" ]
 }
 
 js_library("settings_checkbox") {
   deps = [
-    ":settings_boolean_control_behavior.m",
+    ":settings_boolean_control_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
 }
 
-js_library("settings_dropdown_menu.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/controls/settings_dropdown_menu.m.js" ]
+js_library("settings_dropdown_menu") {
   deps = [
-    ":pref_control_behavior.m",
+    ":pref_control_behavior",
     "../prefs:pref_util.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/policy:cr_policy_indicator_behavior.m",
@@ -112,7 +99,6 @@
     "//ui/webui/resources/js:cr.m",
     "//ui/webui/resources/js:load_time_data.m",
   ]
-  extra_deps = [ ":settings_dropdown_menu_module" ]
 }
 
 js_library("settings_idle_load") {
@@ -122,20 +108,17 @@
   ]
 }
 
-js_library("settings_radio_group.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/controls/settings_radio_group.m.js" ]
+js_library("settings_radio_group") {
   deps = [
-    ":pref_control_behavior.m",
+    ":pref_control_behavior",
     "../prefs:pref_util.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:cr.m",
   ]
-  extra_deps = [ ":settings_radio_group_module" ]
 }
 
-js_library("settings_slider.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/controls/settings_slider.m.js" ]
+js_library("settings_slider") {
   deps = [
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_slider:cr_slider.m",
@@ -143,134 +126,33 @@
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:load_time_data.m",
   ]
-  extra_deps = [ ":settings_slider_module" ]
 }
 
-js_library("settings_textarea.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/controls/settings_textarea.m.js" ]
+js_library("settings_textarea") {
   deps = [
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
-  extra_deps = [ ":settings_textarea_module" ]
 }
 
-js_library("settings_toggle_button.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/controls/settings_toggle_button.m.js" ]
+js_library("settings_toggle_button") {
   deps = [
-    ":settings_boolean_control_behavior.m",
+    ":settings_boolean_control_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_toggle:cr_toggle.m",
   ]
-  extra_deps = [ ":settings_toggle_button_module" ]
-}
-
-group("polymer3_elements") {
-  public_deps = [
-    ":controlled_button_module",
-    ":controlled_radio_button_module",
-    ":extension_controlled_indicator_module",
-    ":modulize",
-    ":password_prompt_dialog_module",
-    ":settings_dropdown_menu_module",
-    ":settings_radio_group_module",
-    ":settings_slider_module",
-    ":settings_textarea_module",
-    ":settings_toggle_button_module",
-    ":web_components",
-  ]
 }
 
 html_to_js("web_components") {
-  js_files = [ "settings_checkbox.js" ]
-}
-
-polymer_modulizer("controlled_button") {
-  js_file = "controlled_button.js"
-  html_file = "controlled_button.html"
-  html_type = "dom-module"
-  auto_imports = settings_auto_imports
-  migrated_imports = settings_migrated_imports
-}
-
-polymer_modulizer("controlled_radio_button") {
-  js_file = "controlled_radio_button.js"
-  html_file = "controlled_radio_button.html"
-  html_type = "dom-module"
-  auto_imports =
-      settings_auto_imports + [
-        "ui/webui/resources/html/assert.html|assert",
-        "chrome/browser/resources/settings/prefs/pref_util.html|prefToString",
-      ]
-  namespace_rewrites = settings_namespace_rewrites
-}
-
-polymer_modulizer("extension_controlled_indicator") {
-  js_file = "extension_controlled_indicator.js"
-  html_file = "extension_controlled_indicator.html"
-  html_type = "dom-module"
-  auto_imports = [
-    "chrome/browser/resources/settings/extension_control_browser_proxy.html|ExtensionControlBrowserProxyImpl",
-    "ui/webui/resources/html/assert.html|assert",
-  ]
-  migrated_imports = settings_migrated_imports
-  namespace_rewrites = settings_namespace_rewrites
-}
-
-polymer_modulizer("password_prompt_dialog") {
-  js_file = "password_prompt_dialog.js"
-  html_file = "password_prompt_dialog.html"
-  html_type = "dom-module"
-}
-
-polymer_modulizer("settings_dropdown_menu") {
-  js_file = "settings_dropdown_menu.js"
-  html_file = "settings_dropdown_menu.html"
-  html_type = "dom-module"
-  migrated_imports = settings_migrated_imports
-  auto_imports = settings_auto_imports + [
-                   "chrome/browser/resources/settings/prefs/pref_util.html|stringToPrefValue, prefToString",
-                   "ui/webui/resources/html/assert.html|assert",
-                 ]
-  namespace_rewrites = settings_namespace_rewrites
-}
-
-polymer_modulizer("settings_radio_group") {
-  js_file = "settings_radio_group.js"
-  html_file = "settings_radio_group.html"
-  html_type = "dom-module"
-  auto_imports = settings_auto_imports + [ "chrome/browser/resources/settings/prefs/pref_util.html|stringToPrefValue, prefToString" ]
-  namespace_rewrites = settings_namespace_rewrites
-}
-
-polymer_modulizer("settings_slider") {
-  js_file = "settings_slider.js"
-  html_file = "settings_slider.html"
-  html_type = "dom-module"
-  migrated_imports = settings_migrated_imports
-  auto_imports =
-      settings_auto_imports + [
-        "ui/webui/resources/html/assert.html|assert",
-        "ui/webui/resources/cr_elements/cr_slider/cr_slider.html|SliderTick",
-      ]
-  namespace_rewrites = settings_namespace_rewrites
-}
-
-polymer_modulizer("settings_textarea") {
-  js_file = "settings_textarea.js"
-  html_file = "settings_textarea.html"
-  html_type = "dom-module"
-}
-
-polymer_modulizer("settings_toggle_button") {
-  js_file = "settings_toggle_button.js"
-  html_file = "settings_toggle_button.html"
-  html_type = "dom-module"
-  auto_imports = settings_auto_imports
-}
-
-js_modulizer("modulize") {
-  input_files = [
-    "pref_control_behavior.js",
-    "settings_boolean_control_behavior.js",
+  js_files = [
+    "controlled_button.js",
+    "controlled_radio_button.js",
+    "extension_controlled_indicator.js",
+    "password_prompt_dialog.js",
+    "settings_checkbox.js",
+    "settings_dropdown_menu.js",
+    "settings_radio_group.js",
+    "settings_slider.js",
+    "settings_textarea.js",
+    "settings_toggle_button.js",
   ]
 }
diff --git a/chrome/browser/resources/settings/controls/controlled_button.html b/chrome/browser/resources/settings/controls/controlled_button.html
index 10af6f6..1a7e926 100644
--- a/chrome/browser/resources/settings/controls/controlled_button.html
+++ b/chrome/browser/resources/settings/controls/controlled_button.html
@@ -1,57 +1,41 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared">
+  :host {
+    --justify-margin: 8px;
+    align-items: center;
+    display: flex;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="pref_control_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
+  :host([enforced_]) {
+    /* Disable pointer events for this whole element, as outer on-click gets
+     * triggered when clicking anywhere in :host. */
+    pointer-events: none;
+  }
 
-<dom-module id="controlled-button">
-  <template>
-    <style include="settings-shared">
-      :host {
-        --justify-margin: 8px;
-        align-items: center;
-        display: flex;
-      }
+  cr-policy-pref-indicator {
+    /* Enable pointer events for the indicator so :hover works. Disable
+     * clicks via onIndicatorClick_ so outer on-click doesn't trigger. */
+    pointer-events: all;
+  }
 
-      :host([enforced_]) {
-        /* Disable pointer events for this whole element, as outer on-click gets
-         * triggered when clicking anywhere in :host. */
-        pointer-events: none;
-      }
+  :host(:not([end-justified])) cr-policy-pref-indicator {
+    margin-inline-start: var(--cr-controlled-by-spacing);
+  }
 
-      cr-policy-pref-indicator {
-        /* Enable pointer events for the indicator so :hover works. Disable
-         * clicks via onIndicatorClick_ so outer on-click doesn't trigger. */
-        pointer-events: all;
-      }
+  :host([end-justified]) cr-policy-pref-indicator {
+    margin-inline-end: var(--cr-controlled-by-spacing);
+    margin-inline-start: calc(
+        var(--cr-controlled-by-spacing) - var(--justify-margin));
+    order: -1;
+  }
+</style>
 
-      :host(:not([end-justified])) cr-policy-pref-indicator {
-        margin-inline-start: var(--cr-controlled-by-spacing);
-      }
+<cr-button class$="[[actionClass_]]"
+  disabled="[[!buttonEnabled_(enforced_, disabled)]]">
+  [[label]]
+</cr-button>
 
-      :host([end-justified]) cr-policy-pref-indicator {
-        margin-inline-end: var(--cr-controlled-by-spacing);
-        margin-inline-start: calc(
-            var(--cr-controlled-by-spacing) - var(--justify-margin));
-        order: -1;
-      }
-    </style>
-
-    <cr-button class$="[[actionClass_]]"
-      disabled="[[!buttonEnabled_(enforced_, disabled)]]">
-      [[label]]
-    </cr-button>
-
-    <template is="dom-if" if="[[hasPrefPolicyIndicator(pref.*)]]" restamp>
-      <cr-policy-pref-indicator pref="[[pref]]" on-click="onIndicatorClick_"
-          icon-aria-label="[[label]]">
-      </cr-policy-pref-indicator>
-    </template>
-
-  </template>
-  <script src="controlled_button.js"></script>
-</dom-module>
+<template is="dom-if" if="[[hasPrefPolicyIndicator(pref.*)]]" restamp>
+  <cr-policy-pref-indicator pref="[[pref]]" on-click="onIndicatorClick_"
+      icon-aria-label="[[label]]">
+  </cr-policy-pref-indicator>
+</template>
diff --git a/chrome/browser/resources/settings/controls/controlled_button.js b/chrome/browser/resources/settings/controls/controlled_button.js
index 213794e..7afcaf85 100644
--- a/chrome/browser/resources/settings/controls/controlled_button.js
+++ b/chrome/browser/resources/settings/controls/controlled_button.js
@@ -2,9 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/policy/cr_policy_pref_indicator.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '../settings_shared_css.m.js';
+
+import {CrPolicyPrefBehavior} from '//resources/cr_elements/policy/cr_policy_pref_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {loadTimeData} from '../i18n_setup.js';
+
+import {PrefControlBehavior} from './pref_control_behavior.js';
+
 Polymer({
   is: 'controlled-button',
 
+  _template: html`{__html_template__}`,
+
   behaviors: [
     CrPolicyPrefBehavior,
     PrefControlBehavior,
diff --git a/chrome/browser/resources/settings/controls/controlled_radio_button.html b/chrome/browser/resources/settings/controls/controlled_radio_button.html
index 071a8943..02264b7 100644
--- a/chrome/browser/resources/settings/controls/controlled_radio_button.html
+++ b/chrome/browser/resources/settings/controls/controlled_radio_button.html
@@ -1,62 +1,45 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared cr-radio-button-style">
+  :host([disabled]) {
+    opacity: 1;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
-<link rel="import" href="pref_control_behavior.html">
-<link rel="import" href="../prefs/pref_util.html">
-<link rel="import" href="../settings_shared_css.html">
+  /* Disc and label should be transluscent, but not the policy indicator. */
+  :host([disabled]) .disc-wrapper,
+  :host([disabled]) #labelWrapper {
+    opacity: var(--cr-disabled-opacity);
+  }
 
-<dom-module id="controlled-radio-button">
-  <template>
-    <style include="settings-shared cr-radio-button-style">
-      :host([disabled]) {
-        opacity: 1;
-      }
+  cr-policy-pref-indicator {
+    margin-inline-start: var(--settings-controlled-by-spacing);
+    /* Enable pointer events for the indicator so :hover works. Disable
+     * clicks/taps via onIndicatorTap_ so outer on-tap doesn't trigger. */
+    pointer-events: all;
+  }
+</style>
 
-      /* Disc and label should be transluscent, but not the policy indicator. */
-      :host([disabled]) .disc-wrapper,
-      :host([disabled]) #labelWrapper {
-        opacity: var(--cr-disabled-opacity);
-      }
+<div
+    aria-checked$="[[getAriaChecked_(checked)]]"
+    aria-describedby="slotted-content"
+    aria-disabled$="[[getAriaDisabled_(disabled)]]"
+    aria-labelledby="label"
+    class="disc-wrapper"
+    id="button"
+    role="radio"
+    tabindex$="[[buttonTabIndex_]]"
+    on-keydown="onInputKeydown_">
+  <div class="disc-border"></div>
+  <div class="disc"></div>
+</div>
 
-      cr-policy-pref-indicator {
-        margin-inline-start: var(--settings-controlled-by-spacing);
-        /* Enable pointer events for the indicator so :hover works. Disable
-         * clicks/taps via onIndicatorTap_ so outer on-tap doesn't trigger. */
-        pointer-events: all;
-      }
-    </style>
+<div id="labelWrapper">
+  <span id="label" hidden$="[[!label]]">[[label]]</span>
+  <span id="slotted-content">
+    <slot></slot>
+  </span>
+</div>
 
-    <div
-        aria-checked$="[[getAriaChecked_(checked)]]"
-        aria-describedby="slotted-content"
-        aria-disabled$="[[getAriaDisabled_(disabled)]]"
-        aria-labelledby="label"
-        class="disc-wrapper"
-        id="button"
-        role="radio"
-        tabindex$="[[buttonTabIndex_]]"
-        on-keydown="onInputKeydown_">
-      <div class="disc-border"></div>
-      <div class="disc"></div>
-    </div>
-
-    <div id="labelWrapper">
-      <span id="label" hidden$="[[!label]]">[[label]]</span>
-      <span id="slotted-content">
-        <slot></slot>
-      </span>
-    </div>
-
-    <template is="dom-if" if="[[showIndicator_(disabled, name, pref.*)]]">
-      <cr-policy-pref-indicator pref="[[pref]]" on-click="onIndicatorTap_"
-          icon-aria-label="[[label]]">
-      </cr-policy-pref-indicator>
-    </template>
-
-  </template>
-  <script src="controlled_radio_button.js"></script>
-</dom-module>
+<template is="dom-if" if="[[showIndicator_(disabled, name, pref.*)]]">
+  <cr-policy-pref-indicator pref="[[pref]]" on-click="onIndicatorTap_"
+      icon-aria-label="[[label]]">
+  </cr-policy-pref-indicator>
+</template>
diff --git a/chrome/browser/resources/settings/controls/controlled_radio_button.js b/chrome/browser/resources/settings/controls/controlled_radio_button.js
index b213d62..5351723 100644
--- a/chrome/browser/resources/settings/controls/controlled_radio_button.js
+++ b/chrome/browser/resources/settings/controls/controlled_radio_button.js
@@ -2,9 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_elements/cr_radio_button/cr_radio_button_style_css.m.js';
+import '//resources/cr_elements/policy/cr_policy_pref_indicator.m.js';
+import '//resources/polymer/v3_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.js';
+import '../settings_shared_css.m.js';
+
+import {CrRadioButtonBehavior} from '//resources/cr_elements/cr_radio_button/cr_radio_button_behavior.m.js';
+import {assert} from '//resources/js/assert.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {prefToString} from '../prefs/pref_util.m.js';
+
+import {PrefControlBehavior} from './pref_control_behavior.js';
+
 Polymer({
   is: 'controlled-radio-button',
 
+  _template: html`{__html_template__}`,
+
   behaviors: [
     PrefControlBehavior,
     CrRadioButtonBehavior,
@@ -25,8 +40,7 @@
    * @private
    */
   showIndicator_() {
-    return this.disabled &&
-        this.name === Settings.PrefUtil.prefToString(assert(this.pref));
+    return this.disabled && this.name === prefToString(assert(this.pref));
   },
 
   /**
diff --git a/chrome/browser/resources/settings/controls/extension_controlled_indicator.html b/chrome/browser/resources/settings/controls/extension_controlled_indicator.html
index 7116518..9dc58b4 100644
--- a/chrome/browser/resources/settings/controls/extension_controlled_indicator.html
+++ b/chrome/browser/resources/settings/controls/extension_controlled_indicator.html
@@ -1,40 +1,26 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared">
+  :host {
+    align-items: center;
+    display: flex;
+    margin-inline-start: 36px;
+    min-height: var(--settings-row-min-height);
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../extension_control_browser_proxy.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../settings_shared_css.html">
+  img {
+    /* Dimensions of the image are set in the URL. */
+    margin-inline-end: 16px;
+  }
 
-<dom-module id="extension-controlled-indicator">
-  <template>
-    <style include="settings-shared">
-      :host {
-        align-items: center;
-        display: flex;
-        margin-inline-start: 36px;
-        min-height: var(--settings-row-min-height);
-      }
-
-      img {
-        /* Dimensions of the image are set in the URL. */
-        margin-inline-end: 16px;
-      }
-
-      /* Using ">" operator to ensure that this CSS rule will not accidentally
-       * be applied to a search highlight span (which is inserted dynamically if
-       * when search "hit" occurs within this element. */
-      :host > span {
-        flex: 1;
-        margin-inline-end: 8px;
-      }
-    </style>
-    <img role="presentation" src="chrome://extension-icon/[[extensionId]]/20/1">
-    <span inner-h-t-m-l="[[getLabel_(extensionId, extensionName)]]"></span>
-    <template is="dom-if" if="[[extensionCanBeDisabled]]" restamp>
-      <cr-button on-click="onDisableTap_">$i18n{disable}</cr-button>
-    </template>
-  </template>
-  <script src="extension_controlled_indicator.js"></script>
-</dom-module>
+  /* Using ">" operator to ensure that this CSS rule will not accidentally
+   * be applied to a search highlight span (which is inserted dynamically if
+   * when search "hit" occurs within this element. */
+  :host > span {
+    flex: 1;
+    margin-inline-end: 8px;
+  }
+</style>
+<img role="presentation" src="chrome://extension-icon/[[extensionId]]/20/1">
+<span inner-h-t-m-l="[[getLabel_(extensionId, extensionName)]]"></span>
+<template is="dom-if" if="[[extensionCanBeDisabled]]" restamp>
+  <cr-button on-click="onDisableTap_">$i18n{disable}</cr-button>
+</template>
diff --git a/chrome/browser/resources/settings/controls/extension_controlled_indicator.js b/chrome/browser/resources/settings/controls/extension_controlled_indicator.js
index 5b0681f..9c61dd6 100644
--- a/chrome/browser/resources/settings/controls/extension_controlled_indicator.js
+++ b/chrome/browser/resources/settings/controls/extension_controlled_indicator.js
@@ -2,9 +2,21 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '../i18n_setup.js';
+import '../settings_shared_css.m.js';
+
+import {assert} from '//resources/js/assert.m.js';
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {ExtensionControlBrowserProxyImpl} from '../extension_control_browser_proxy.m.js';
+
 Polymer({
   is: 'extension-controlled-indicator',
 
+  _template: html`{__html_template__}`,
+
   behaviors: [I18nBehavior],
 
   properties: {
@@ -35,7 +47,7 @@
   /** @private */
   onDisableTap_() {
     assert(this.extensionCanBeDisabled);
-    settings.ExtensionControlBrowserProxyImpl.getInstance().disableExtension(
+    ExtensionControlBrowserProxyImpl.getInstance().disableExtension(
         assert(this.extensionId));
     this.fire('extension-disable');
   },
diff --git a/chrome/browser/resources/settings/controls/password_prompt_dialog.html b/chrome/browser/resources/settings/controls/password_prompt_dialog.html
index c7e3ced7..14501ba 100644
--- a/chrome/browser/resources/settings/controls/password_prompt_dialog.html
+++ b/chrome/browser/resources/settings/controls/password_prompt_dialog.html
@@ -1,52 +1,39 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="cr-shared-style settings-shared">
+  cr-dialog::part(dialog) {
+    width: 320px;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="../settings_shared_css.html">
+  #passwordPrompt {
+    padding-bottom: 20px;
+    padding-inline-end: 0;
+    padding-inline-start: 0;
+  }
+</style>
+<cr-dialog id="dialog" close-text="$i18n{close}">
+  <div slot="title">$i18n{passwordPromptTitle}</div>
+  <div slot="body">
+    <div id="passwordPrompt" class="cr-row first"
+        hidden="[[!passwordPromptText]]">
+      [[passwordPromptText]]
+    </div>
+    <cr-input id="passwordInput" type="password"
+        placeholder="$i18n{passwordPromptPasswordLabel}"
+        invalid="[[passwordInvalid_]]"
+        error-message="$i18n{passwordPromptInvalidPassword}"
+        value="{{inputValue_}}"
+        aria-disabled="false">
+    </cr-input>
+  </div>
+  <div slot="button-container">
+    <cr-button class="cancel-button" on-click="onCancelTap_">
+      $i18n{cancel}
+    </cr-button>
 
-<dom-module id="settings-password-prompt-dialog">
-  <template>
-    <style include="cr-shared-style settings-shared">
-      cr-dialog::part(dialog) {
-        width: 320px;
-      }
-
-      #passwordPrompt {
-        padding-bottom: 20px;
-        padding-inline-end: 0;
-        padding-inline-start: 0;
-      }
-    </style>
-    <cr-dialog id="dialog" close-text="$i18n{close}">
-      <div slot="title">$i18n{passwordPromptTitle}</div>
-      <div slot="body">
-        <div id="passwordPrompt" class="cr-row first"
-            hidden="[[!passwordPromptText]]">
-          [[passwordPromptText]]
-        </div>
-        <cr-input id="passwordInput" type="password"
-            placeholder="$i18n{passwordPromptPasswordLabel}"
-            invalid="[[passwordInvalid_]]"
-            error-message="$i18n{passwordPromptInvalidPassword}"
-            value="{{inputValue_}}"
-            aria-disabled="false">
-        </cr-input>
-      </div>
-      <div slot="button-container">
-        <cr-button class="cancel-button" on-click="onCancelTap_">
-          $i18n{cancel}
-        </cr-button>
-
-        <cr-button id="confirmButton" class="action-button"
-            disabled$="[[!isConfirmEnabled_(inputValue_, passwordInvalid_,
-                waitingForPasswordCheck_)]]"
-            on-click="submitPassword_">
-          $i18n{confirm}
-        </cr-button>
-      </div>
-    </cr-dialog>
-  </template>
-  <script src="password_prompt_dialog.js"></script>
-</dom-module>
+    <cr-button id="confirmButton" class="action-button"
+        disabled$="[[!isConfirmEnabled_(inputValue_, passwordInvalid_,
+            waitingForPasswordCheck_)]]"
+        on-click="submitPassword_">
+      $i18n{confirm}
+    </cr-button>
+  </div>
+</cr-dialog>
diff --git a/chrome/browser/resources/settings/controls/password_prompt_dialog.js b/chrome/browser/resources/settings/controls/password_prompt_dialog.js
index 872cb3d..0450b16 100644
--- a/chrome/browser/resources/settings/controls/password_prompt_dialog.js
+++ b/chrome/browser/resources/settings/controls/password_prompt_dialog.js
@@ -19,9 +19,19 @@
  * </settings-password-prompt-dialog>
  */
 
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import '//resources/cr_elements/cr_input/cr_input.m.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '../settings_shared_css.m.js';
+
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 Polymer({
   is: 'settings-password-prompt-dialog',
 
+  _template: html`{__html_template__}`,
+
   properties: {
     /**
      * The subtext to be displayed above the password input field. Embedders
diff --git a/chrome/browser/resources/settings/controls/pref_control_behavior.html b/chrome/browser/resources/settings/controls/pref_control_behavior.html
deleted file mode 100644
index 71841dc..0000000
--- a/chrome/browser/resources/settings/controls/pref_control_behavior.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="../prefs/prefs_types.html">
-<script src="pref_control_behavior.js"></script>
diff --git a/chrome/browser/resources/settings/controls/pref_control_behavior.js b/chrome/browser/resources/settings/controls/pref_control_behavior.js
index 922b742..378c7edc 100644
--- a/chrome/browser/resources/settings/controls/pref_control_behavior.js
+++ b/chrome/browser/resources/settings/controls/pref_control_behavior.js
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// #import {CrSettingsPrefs} from '../prefs/prefs_types.m.js';
+import {CrSettingsPrefs} from '../prefs/prefs_types.m.js';
 
 /**
  * @polymerBehavior Tracks the initialization of a specified preference and
  * logs an error if the pref is not defined after prefs have been fetched.
  */
-/* #export */ const PrefControlBehavior = {
+export const PrefControlBehavior = {
   properties: {
     /**
      * The Preference object being tracked.
diff --git a/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.html b/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.html
deleted file mode 100644
index c0b6c57..0000000
--- a/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_behavior.html">
-<link rel="import" href="pref_control_behavior.html">
-<script src="settings_boolean_control_behavior.js"></script>
diff --git a/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.js b/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.js
index 7cda6e36..08615e9 100644
--- a/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.js
+++ b/chrome/browser/resources/settings/controls/settings_boolean_control_behavior.js
@@ -3,9 +3,10 @@
 // found in the LICENSE file.
 
 // clang-format off
-// #import {assert} from 'chrome://resources/js/assert.m.js';
-// #import {CrPolicyPrefBehavior} from 'chrome://resources/cr_elements/policy/cr_policy_pref_behavior.m.js';
-// #import {PrefControlBehavior} from './pref_control_behavior.m.js';
+import {CrPolicyPrefBehavior} from 'chrome://resources/cr_elements/policy/cr_policy_pref_behavior.m.js';
+import {assert} from 'chrome://resources/js/assert.m.js';
+
+import {PrefControlBehavior} from './pref_control_behavior.js';
 // clang-format on
 
 /**
@@ -138,7 +139,7 @@
 };
 
 /** @polymerBehavior */
-/* #export */ const SettingsBooleanControlBehavior = [
+export const SettingsBooleanControlBehavior = [
   CrPolicyPrefBehavior,
   PrefControlBehavior,
   SettingsBooleanControlBehaviorImpl,
diff --git a/chrome/browser/resources/settings/controls/settings_checkbox.html b/chrome/browser/resources/settings/controls/settings_checkbox.html
index 772dbf6..74f8601 100644
--- a/chrome/browser/resources/settings/controls/settings_checkbox.html
+++ b/chrome/browser/resources/settings/controls/settings_checkbox.html
@@ -1,39 +1,39 @@
-    <style include="settings-shared">
-      #outerRow {
-        align-items: center;
-        display: flex;
-        min-height: var(--settings-row-two-line-min-height);
-        width: 100%;
-      }
+<style include="settings-shared">
+  #outerRow {
+    align-items: center;
+    display: flex;
+    min-height: var(--settings-row-two-line-min-height);
+    width: 100%;
+  }
 
-      #outerRow[noSubLabel] {
-        min-height: var(--settings-row-min-height);
-      }
+  #outerRow[noSubLabel] {
+    min-height: var(--settings-row-min-height);
+  }
 
-      cr-checkbox {
-        /* Additional margin in case subLabel needs more than one line. */
-        margin-bottom: 4px;
-        margin-top: var(--settings-checkbox-margin-top, 4px);
-        width: 100%;
-      }
+  cr-checkbox {
+    /* Additional margin in case subLabel needs more than one line. */
+    margin-bottom: 4px;
+    margin-top: var(--settings-checkbox-margin-top, 4px);
+    width: 100%;
+  }
 
-      cr-policy-pref-indicator {
-        margin-inline-start: var(--settings-controlled-by-spacing);
-      }
-    </style>
-    <div id="outerRow" noSubLabel$="[[!hasSubLabel_(subLabel, subLabelHtml)]]">
-      <cr-checkbox id="checkbox" checked="{{checked}}"
-          on-change="notifyChangedByUserInteraction"
-          disabled="[[controlDisabled(disabled, pref.*)]]"
-          aria-label="[[label]]">
-        <div id="label" class="label">[[label]] <slot></slot></div>
-        <div id="subLabel" class="secondary label">
-          <div inner-h-t-m-l="[[subLabelHtml]]"></div>
-          [[subLabel]]
-        </div>
-      </cr-checkbox>
-      <template is="dom-if" if="[[pref.controlledBy]]">
-        <cr-policy-pref-indicator pref="[[pref]]" icon-aria-label="[[label]]">
-        </cr-policy-pref-indicator>
-      </template>
+  cr-policy-pref-indicator {
+    margin-inline-start: var(--settings-controlled-by-spacing);
+  }
+</style>
+<div id="outerRow" noSubLabel$="[[!hasSubLabel_(subLabel, subLabelHtml)]]">
+  <cr-checkbox id="checkbox" checked="{{checked}}"
+      on-change="notifyChangedByUserInteraction"
+      disabled="[[controlDisabled(disabled, pref.*)]]"
+      aria-label="[[label]]">
+    <div id="label" class="label">[[label]] <slot></slot></div>
+    <div id="subLabel" class="secondary label">
+      <div inner-h-t-m-l="[[subLabelHtml]]"></div>
+      [[subLabel]]
     </div>
+  </cr-checkbox>
+  <template is="dom-if" if="[[pref.controlledBy]]">
+    <cr-policy-pref-indicator pref="[[pref]]" icon-aria-label="[[label]]">
+    </cr-policy-pref-indicator>
+  </template>
+</div>
diff --git a/chrome/browser/resources/settings/controls/settings_checkbox.js b/chrome/browser/resources/settings/controls/settings_checkbox.js
index 98a1503..f9004e9 100644
--- a/chrome/browser/resources/settings/controls/settings_checkbox.js
+++ b/chrome/browser/resources/settings/controls/settings_checkbox.js
@@ -12,7 +12,7 @@
 
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {SettingsBooleanControlBehavior} from './settings_boolean_control_behavior.m.js';
+import {SettingsBooleanControlBehavior} from './settings_boolean_control_behavior.js';
 
 Polymer({
   is: 'settings-checkbox',
diff --git a/chrome/browser/resources/settings/controls/settings_dropdown_menu.html b/chrome/browser/resources/settings/controls/settings_dropdown_menu.html
index 626fa45..7295c85e 100644
--- a/chrome/browser/resources/settings/controls/settings_dropdown_menu.html
+++ b/chrome/browser/resources/settings/controls/settings_dropdown_menu.html
@@ -1,53 +1,36 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared md-select">
+  :host {
+    align-items: center;
+    display: inline-flex;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="pref_control_behavior.html">
-<link rel="import" href="../i18n_setup.html">
-<link rel="import" href="../prefs/pref_util.html">
-<link rel="import" href="../settings_shared_css.html">
-<link rel="import" href="../settings_vars_css.html">
+  /* When settings-dropdown-menu is start-aligned, we probably want policy
+   * indicator to be be displayed after the dropdown.
+   * Setting --settings-dropdown-menu-policy-order to 1 will do the job.
+   */
+  cr-policy-pref-indicator {
+    height: var(--iron-icon-width, 24px);
+    margin: 0 var(--settings-controlled-by-spacing);
+    order: var(--settings-dropdown-menu-policy-order, 0);
+    width: var(--iron-icon-width, 24px);
+  }
 
-<dom-module id="settings-dropdown-menu">
-  <template>
-    <style include="settings-shared md-select">
-      :host {
-        align-items: center;
-        display: inline-flex;
-      }
-
-      /* When settings-dropdown-menu is start-aligned, we probably want policy
-       * indicator to be be displayed after the dropdown.
-       * Setting --settings-dropdown-menu-policy-order to 1 will do the job.
-       */
-      cr-policy-pref-indicator {
-        height: var(--iron-icon-width, 24px);
-        margin: 0 var(--settings-controlled-by-spacing);
-        order: var(--settings-dropdown-menu-policy-order, 0);
-        width: var(--iron-icon-width, 24px);
-      }
-
-      /* Hide "Custom" value when unselectable. */
-      option:disabled {
-        display: none;
-      }
-    </style>
-    <template is="dom-if" if="[[pref.controlledBy]]" restamp>
-      <cr-policy-pref-indicator pref="[[pref]]"></cr-policy-pref-indicator>
-    </template>
-    <select class="md-select" id="dropdownMenu" on-change="onChange_"
-        aria-label$="[[label]]"
-        disabled="[[shouldDisableMenu_(disabled, menuOptions.*, pref.*)]]">
-      <template is="dom-repeat" items="[[menuOptions]]">
-        <option value="[[item.value]]">[[item.name]]</option>
-      </template>
-      <option value="[[notFoundValue_]]"
-          disabled="[[!showNotFoundValue_(menuOptions, pref.value)]]">
-        $i18n{custom}
-      </option>
-    </select>
+  /* Hide "Custom" value when unselectable. */
+  option:disabled {
+    display: none;
+  }
+</style>
+<template is="dom-if" if="[[pref.controlledBy]]" restamp>
+  <cr-policy-pref-indicator pref="[[pref]]"></cr-policy-pref-indicator>
+</template>
+<select class="md-select" id="dropdownMenu" on-change="onChange_"
+    aria-label$="[[label]]"
+    disabled="[[shouldDisableMenu_(disabled, menuOptions.*, pref.*)]]">
+  <template is="dom-repeat" items="[[menuOptions]]">
+    <option value="[[item.value]]">[[item.name]]</option>
   </template>
-  <script src="settings_dropdown_menu.js"></script>
-</dom-module>
+  <option value="[[notFoundValue_]]"
+      disabled="[[!showNotFoundValue_(menuOptions, pref.value)]]">
+    $i18n{custom}
+  </option>
+</select>
diff --git a/chrome/browser/resources/settings/controls/settings_dropdown_menu.js b/chrome/browser/resources/settings/controls/settings_dropdown_menu.js
index 395643d..aee19ae1 100644
--- a/chrome/browser/resources/settings/controls/settings_dropdown_menu.js
+++ b/chrome/browser/resources/settings/controls/settings_dropdown_menu.js
@@ -3,6 +3,29 @@
 // found in the LICENSE file.
 
 /**
+ * 'settings-dropdown-menu' is a control for displaying options
+ * in the settings.
+ *
+ * Example:
+ *
+ *   <settings-dropdown-menu pref="{{prefs.foo}}">
+ *   </settings-dropdown-menu>
+ */
+import '//resources/cr_elements/md_select_css.m.js';
+import '//resources/cr_elements/policy/cr_policy_pref_indicator.m.js';
+import '../settings_shared_css.m.js';
+import '../settings_vars_css.m.js';
+
+import {CrPolicyPrefBehavior} from '//resources/cr_elements/policy/cr_policy_pref_behavior.m.js';
+import {assert} from '//resources/js/assert.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {loadTimeData} from '../i18n_setup.js';
+import {prefToString, stringToPrefValue} from '../prefs/pref_util.m.js';
+
+import {PrefControlBehavior} from './pref_control_behavior.js';
+
+/**
  * The |name| is shown in the gui.  The |value| us use to set or compare with
  * the preference value.
  * @typedef {{
@@ -15,20 +38,13 @@
 /**
  * @typedef {!Array<!DropdownMenuOption>}
  */
-/* #export */ let DropdownMenuOptionList;
+export let DropdownMenuOptionList;
 
-/**
- * 'settings-dropdown-menu' is a control for displaying options
- * in the settings.
- *
- * Example:
- *
- *   <settings-dropdown-menu pref="{{prefs.foo}}">
- *   </settings-dropdown-menu>
- */
 Polymer({
   is: 'settings-dropdown-menu',
 
+  _template: html`{__html_template__}`,
+
   behaviors: [CrPolicyPrefBehavior, PrefControlBehavior],
 
   properties: {
@@ -92,8 +108,7 @@
       assert(this.pref);
       this.set(`pref.value.${this.prefKey}`, selected);
     } else {
-      const prefValue =
-          Settings.PrefUtil.stringToPrefValue(selected, assert(this.pref));
+      const prefValue = stringToPrefValue(selected, assert(this.pref));
       if (prefValue !== undefined) {
         this.set('pref.value', prefValue);
       }
@@ -141,7 +156,7 @@
       // Dictionary pref, values are always strings.
       return this.pref.value[this.prefKey];
     } else {
-      return Settings.PrefUtil.prefToString(assert(this.pref));
+      return prefToString(assert(this.pref));
     }
   },
 
diff --git a/chrome/browser/resources/settings/controls/settings_radio_group.html b/chrome/browser/resources/settings/controls/settings_radio_group.html
index ce838b4..17d74f3f 100644
--- a/chrome/browser/resources/settings/controls/settings_radio_group.html
+++ b/chrome/browser/resources/settings/controls/settings_radio_group.html
@@ -1,20 +1,7 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
-<link rel="import" href="pref_control_behavior.html">
-<link rel="import" href="../prefs/pref_util.html">
-<link rel="import" href="../settings_shared_css.html">
-
-<dom-module id="settings-radio-group">
-  <template>
-    <style include="settings-shared"></style>
-    <cr-radio-group selected="[[selected]]"
-        on-selected-changed="onSelectedChanged_"
-        aria-label$="[[groupAriaLabel]]"
-        selectable-elements="[[selectableElements]]">
-      <slot></slot>
-    </cr-radio-group>
-  </template>
-  <script src="settings_radio_group.js"></script>
-</dom-module>
+<style include="settings-shared"></style>
+<cr-radio-group selected="[[selected]]"
+    on-selected-changed="onSelectedChanged_"
+    aria-label$="[[groupAriaLabel]]"
+    selectable-elements="[[selectableElements]]">
+  <slot></slot>
+</cr-radio-group>
diff --git a/chrome/browser/resources/settings/controls/settings_radio_group.js b/chrome/browser/resources/settings/controls/settings_radio_group.js
index 0d7369a..86ea567f 100644
--- a/chrome/browser/resources/settings/controls/settings_radio_group.js
+++ b/chrome/browser/resources/settings/controls/settings_radio_group.js
@@ -12,9 +12,21 @@
  *          label="Foo Options." buttons="{{fooOptionsList}}">
  *      </settings-radio-group>
  */
+import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
+import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import '../settings_shared_css.m.js';
+
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {prefToString, stringToPrefValue} from '../prefs/pref_util.m.js';
+
+import {PrefControlBehavior} from './pref_control_behavior.js';
+
 Polymer({
   is: 'settings-radio-group',
 
+  _template: html`{__html_template__}`,
+
   behaviors: [PrefControlBehavior],
 
   properties: {
@@ -54,7 +66,7 @@
   /** Reset the selected value to match the current pref value. */
   resetToPrefValue() {
     const pref = /** @type {!chrome.settingsPrivate.PrefObject} */ (this.pref);
-    this.selected = Settings.PrefUtil.prefToString(pref);
+    this.selected = prefToString(pref);
   },
 
   /** Update the pref to the current selected value. */
@@ -62,9 +74,7 @@
     if (!this.pref) {
       return;
     }
-    this.set(
-        'pref.value',
-        Settings.PrefUtil.stringToPrefValue(this.selected, this.pref));
+    this.set('pref.value', stringToPrefValue(this.selected, this.pref));
   },
 
   /** @private */
diff --git a/chrome/browser/resources/settings/controls/settings_slider.html b/chrome/browser/resources/settings/controls/settings_slider.html
index 0234311..a1ae387 100644
--- a/chrome/browser/resources/settings/controls/settings_slider.html
+++ b/chrome/browser/resources/settings/controls/settings_slider.html
@@ -1,77 +1,64 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style>
+  :host {
+    display: inline-flex;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_behavior.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_slider/cr_slider.html">
-<link rel="import" href="../settings_vars_css.html">
-<link rel="import" href="../i18n_setup.html">
+  cr-policy-pref-indicator {
+    align-self: center;
+    margin-inline-start: var(--settings-controlled-by-spacing);
+  }
 
-<dom-module id="settings-slider">
-  <template>
-    <style>
-      :host {
-        display: inline-flex;
-      }
+  #labels[disabled] {
+    color: var(--paper-grey-400);
+  }
 
-      cr-policy-pref-indicator {
-        align-self: center;
-        margin-inline-start: var(--settings-controlled-by-spacing);
-      }
+  @media (prefers-color-scheme: dark) {
+    #labels[disabled] {
+      color: var(--google-grey-refresh-500);
+    }
+  }
 
-      #labels[disabled] {
-        color: var(--paper-grey-400);
-      }
+  div.outer {
+    align-items: stretch;
+    display: flex;
+    flex-direction: column;
+    margin: 8px 0;
+    min-width: 200px;
+  }
 
-      @media (prefers-color-scheme: dark) {
-        #labels[disabled] {
-          color: var(--google-grey-refresh-500);
-        }
-      }
+  #labels {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    margin: -4px 16px 0 16px;
+  }
 
-      div.outer {
-        align-items: stretch;
-        display: flex;
-        flex-direction: column;
-        margin: 8px 0;
-        min-width: 200px;
-      }
+  #labels > div {
+    font-size: 12px;
+  }
 
-      #labels {
-        display: flex;
-        flex-direction: row;
-        justify-content: space-between;
-        margin: -4px 16px 0 16px;
-      }
+  #label-begin {
+    margin-inline-end: 4px;
+  }
 
-      #labels > div {
-        font-size: 12px;
-      }
-
-      #label-begin {
-        margin-inline-end: 4px;
-      }
-
-      #label-end {
-        margin-inline-start: 4px;
-      }
-    </style>
-    <template is="dom-if" if="[[pref.controlledBy]]" restamp>
-      <cr-policy-pref-indicator pref="[[pref]]"></cr-policy-pref-indicator>
-    </template>
-    <div class="outer">
-      <cr-slider id="slider" disabled$="[[disableSlider_]]" ticks="[[ticks]]"
-          on-cr-slider-value-changed="onSliderChanged_" max="[[max]]"
-          min="[[min]]" on-dragging-changed="onSliderChanged_"
-          on-updating-from-key="onSliderChanged_"
-          aria-roledescription$="[[getRoleDescription_()]]"
-          aria-label$="[[labelAria]]">
-      </cr-slider>
-      <!-- aria-hidden because role description on #slider contains min/max. -->
-      <div id="labels" disabled$="[[disableSlider_]]" aria-hidden="true">
-        <div id="label-begin">[[labelMin]]</div>
-        <div id="label-end">[[labelMax]]</div>
-      </div>
-    </div>
-  </template>
-  <script src="settings_slider.js"></script>
-</dom-module>
+  #label-end {
+    margin-inline-start: 4px;
+  }
+</style>
+<template is="dom-if" if="[[pref.controlledBy]]" restamp>
+  <cr-policy-pref-indicator pref="[[pref]]"></cr-policy-pref-indicator>
+</template>
+<div class="outer">
+  <cr-slider id="slider" disabled$="[[disableSlider_]]" ticks="[[ticks]]"
+      on-cr-slider-value-changed="onSliderChanged_" max="[[max]]"
+      min="[[min]]" on-dragging-changed="onSliderChanged_"
+      on-updating-from-key="onSliderChanged_"
+      aria-roledescription$="[[getRoleDescription_()]]"
+      aria-label$="[[labelAria]]">
+  </cr-slider>
+  <!-- aria-hidden because role description on #slider contains min/max. -->
+  <div id="labels" disabled$="[[disableSlider_]]" aria-hidden="true">
+    <div id="label-begin">[[labelMin]]</div>
+    <div id="label-end">[[labelMax]]</div>
+  </div>
+</div>
diff --git a/chrome/browser/resources/settings/controls/settings_slider.js b/chrome/browser/resources/settings/controls/settings_slider.js
index cc7f409..539e3bb 100644
--- a/chrome/browser/resources/settings/controls/settings_slider.js
+++ b/chrome/browser/resources/settings/controls/settings_slider.js
@@ -8,9 +8,20 @@
  * linear UI range to a range of real values.  When |value| does not map exactly
  * to a tick mark, it interpolates to the nearest tick.
  */
+import '../settings_vars_css.m.js';
+
+import {SliderTick} from '//resources/cr_elements/cr_slider/cr_slider.m.js';
+import {CrPolicyPrefBehavior} from '//resources/cr_elements/policy/cr_policy_pref_behavior.m.js';
+import {assert} from '//resources/js/assert.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {loadTimeData} from '../i18n_setup.js';
+
 Polymer({
   is: 'settings-slider',
 
+  _template: html`{__html_template__}`,
+
   behaviors: [CrPolicyPrefBehavior],
 
   properties: {
@@ -19,7 +30,7 @@
 
     /**
      * Values corresponding to each tick.
-     * @type {!Array<cr_slider.SliderTick>|!Array<number>}
+     * @type {!Array<SliderTick>|!Array<number>}
      */
     ticks: {
       type: Array,
@@ -79,7 +90,7 @@
   },
 
   /**
-   * @param {number|cr_slider.SliderTick} tick
+   * @param {number|SliderTick} tick
    * @return {number|undefined}
    */
   getTickValue_(tick) {
diff --git a/chrome/browser/resources/settings/controls/settings_textarea.html b/chrome/browser/resources/settings/controls/settings_textarea.html
index 0d6cf96..b509107 100644
--- a/chrome/browser/resources/settings/controls/settings_textarea.html
+++ b/chrome/browser/resources/settings/controls/settings_textarea.html
@@ -1,38 +1,27 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="cr-hidden-style cr-input-style cr-shared-style">
+  textarea {
+    display: block;
+    resize: none;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input_style_css.html">
+  #input-container {
+    background-color: var(--cr-input-background-color);
+  }
 
-<dom-module id="settings-textarea">
-  <template>
-    <style include="cr-hidden-style cr-input-style cr-shared-style">
-      textarea {
-        display: block;
-        resize: none;
-      }
-
-      #input-container {
-        background-color: var(--cr-input-background-color);
-      }
-
-      #underline {
-        position: static;
-      }
-    </style>
-    <div id="label" class="cr-form-field-label" hidden="[[!label]]">
-      [[label]]
-    </div>
-    <div id="input-container">
-      <!-- The textarea is limited to |rows| height. If the content exceeds the
-           bounds, it scrolls by default. No space or comments are allowed
-           before the closing tag. -->
-      <textarea id="input" autofocus="[[autofocus]]" rows="[[rows]]"
-          value="{{value::input}}" aria-label$="[[label]]"
-          on-focus="onInputFocusChange_" on-blur="onInputFocusChange_"
-          on-change="onInputChange_" disabled="[[disabled]]"></textarea>
-      <div id="underline"></div>
-    </div>
-  </template>
-  <script src="settings_textarea.js"></script>
-</dom-module>
+  #underline {
+    position: static;
+  }
+</style>
+<div id="label" class="cr-form-field-label" hidden="[[!label]]">
+  [[label]]
+</div>
+<div id="input-container">
+  <!-- The textarea is limited to |rows| height. If the content exceeds the
+       bounds, it scrolls by default. No space or comments are allowed
+       before the closing tag. -->
+  <textarea id="input" autofocus="[[autofocus]]" rows="[[rows]]"
+      value="{{value::input}}" aria-label$="[[label]]"
+      on-focus="onInputFocusChange_" on-blur="onInputFocusChange_"
+      on-change="onInputChange_" disabled="[[disabled]]"></textarea>
+  <div id="underline"></div>
+</div>
diff --git a/chrome/browser/resources/settings/controls/settings_textarea.js b/chrome/browser/resources/settings/controls/settings_textarea.js
index 2deabe3..e7d4d3b 100644
--- a/chrome/browser/resources/settings/controls/settings_textarea.js
+++ b/chrome/browser/resources/settings/controls/settings_textarea.js
@@ -6,9 +6,17 @@
  * @fileoverview 'settings-textarea' is a component similar to native textarea,
  * and inherits styling from cr-input.
  */
+import '//resources/cr_elements/hidden_style_css.m.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '//resources/cr_elements/cr_input/cr_input_style_css.m.js';
+
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 Polymer({
   is: 'settings-textarea',
 
+  _template: html`{__html_template__}`,
+
   properties: {
     /**
      * Whether the text area should automatically get focus when the page
diff --git a/chrome/browser/resources/settings/controls/settings_toggle_button.html b/chrome/browser/resources/settings/controls/settings_toggle_button.html
index 92afe32..0818cb4 100644
--- a/chrome/browser/resources/settings/controls/settings_toggle_button.html
+++ b/chrome/browser/resources/settings/controls/settings_toggle_button.html
@@ -1,84 +1,69 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="cr-actionable-row-style settings-shared iron-flex">
+  :host {
+    padding: 0 var(--cr-section-padding);
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_actionable_row_style.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="settings_boolean_control_behavior.html">
-<link rel="import" href="../settings_shared_css.html">
+  :host([elide-label]),
+  :host([elide-label]) #outerRow,
+  :host([elide-label]) #outerRow > div.flex {
+    min-width: 0;
+  }
 
-<dom-module id="settings-toggle-button">
-  <template>
-    <style include="cr-actionable-row-style settings-shared iron-flex">
-      :host {
-        padding: 0 var(--cr-section-padding);
-      }
+  :host([elide-label]) .label {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
 
-      :host([elide-label]),
-      :host([elide-label]) #outerRow,
-      :host([elide-label]) #outerRow > div.flex {
-        min-width: 0;
-      }
+  #outerRow {
+    align-items: center;
+    display: flex;
+    min-height: var(--settings-row-two-line-min-height);
+    width: 100%;
+  }
 
-      :host([elide-label]) .label {
-        overflow: hidden;
-        text-overflow: ellipsis;
-        white-space: nowrap;
-      }
+  #outerRow[noSubLabel] {
+    min-height: var(--settings-row-min-height);
+  }
 
-      #outerRow {
-        align-items: center;
-        display: flex;
-        min-height: var(--settings-row-two-line-min-height);
-        width: 100%;
-      }
+  #labelWrapper {
+    padding: var(--cr-section-vertical-padding) 0;
+  }
 
-      #outerRow[noSubLabel] {
-        min-height: var(--settings-row-min-height);
-      }
+  #labelWrapper,
+  ::slotted([slot='more-actions']) {
+    margin-inline-end: var(--settings-control-label-spacing) !important;
+  }
 
-      #labelWrapper {
-        padding: var(--cr-section-vertical-padding) 0;
-      }
-
-      #labelWrapper,
-      ::slotted([slot='more-actions']) {
-        margin-inline-end: var(--settings-control-label-spacing) !important;
-      }
-
-      cr-policy-pref-indicator {
-        margin-inline-end: var(--settings-controlled-by-spacing);
-      }
-    </style>
-    <div id="outerRow" noSubLabel$="[[!subLabel]]">
-      <div class="flex" id="labelWrapper" hidden$="[[!label]]">
-        <div class="label" aria-hidden="true">[[label]]</div>
-        <div class="secondary label" id="sub-label">
-          <span id="sub-label-text" aria-hidden="true">
-            [[subLabel]]
-          </span>
-          <template is="dom-if" if="[[learnMoreUrl]]">
-            <a id="learn-more" href="[[learnMoreUrl]]" target="_blank"
-                aria-labeledby="sub-label-text learn-more"
-                on-click="onLearnMoreClicked_">
-              $i18n{learnMore}
-            </a>
-          </template>
-        </div>
-      </div>
-      <slot name="more-actions"></slot>
-      <template is="dom-if" if="[[hasPrefPolicyIndicator(pref.*)]]">
-        <cr-policy-pref-indicator pref="[[pref]]" icon-aria-label="[[label]]">
-        </cr-policy-pref-indicator>
+  cr-policy-pref-indicator {
+    margin-inline-end: var(--settings-controlled-by-spacing);
+  }
+</style>
+<div id="outerRow" noSubLabel$="[[!subLabel]]">
+  <div class="flex" id="labelWrapper" hidden$="[[!label]]">
+    <div class="label" aria-hidden="true">[[label]]</div>
+    <div class="secondary label" id="sub-label">
+      <span id="sub-label-text" aria-hidden="true">
+        [[subLabel]]
+      </span>
+      <template is="dom-if" if="[[learnMoreUrl]]">
+        <a id="learn-more" href="[[learnMoreUrl]]" target="_blank"
+            aria-labeledby="sub-label-text learn-more"
+            on-click="onLearnMoreClicked_">
+          $i18n{learnMore}
+        </a>
       </template>
-      <cr-toggle id="control" checked="{{checked}}"
-          on-change="onChange_"
-          aria-label$="[[getAriaLabel_(label, ariaLabel)]]"
-          aria-describedby="sub-label-text"
-          disabled="[[controlDisabled(disabled, pref)]]">
-      </cr-toggle>
     </div>
+  </div>
+  <slot name="more-actions"></slot>
+  <template is="dom-if" if="[[hasPrefPolicyIndicator(pref.*)]]">
+    <cr-policy-pref-indicator pref="[[pref]]" icon-aria-label="[[label]]">
+    </cr-policy-pref-indicator>
   </template>
-  <script src="settings_toggle_button.js"></script>
-</dom-module>
+  <cr-toggle id="control" checked="{{checked}}"
+      on-change="onChange_"
+      aria-label$="[[getAriaLabel_(label, ariaLabel)]]"
+      aria-describedby="sub-label-text"
+      disabled="[[controlDisabled(disabled, pref)]]">
+  </cr-toggle>
+</div>
diff --git a/chrome/browser/resources/settings/controls/settings_toggle_button.js b/chrome/browser/resources/settings/controls/settings_toggle_button.js
index 08eba75..bd3a3b4 100644
--- a/chrome/browser/resources/settings/controls/settings_toggle_button.js
+++ b/chrome/browser/resources/settings/controls/settings_toggle_button.js
@@ -6,9 +6,21 @@
  * @fileoverview
  * `settings-toggle-button` is a toggle that controls a supplied preference.
  */
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '//resources/cr_elements/cr_toggle/cr_toggle.m.js';
+import '//resources/cr_elements/policy/cr_policy_pref_indicator.m.js';
+import '//resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
+import '../settings_shared_css.m.js';
+
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {SettingsBooleanControlBehavior} from './settings_boolean_control_behavior.js';
+
 Polymer({
   is: 'settings-toggle-button',
 
+  _template: html`{__html_template__}`,
+
   behaviors: [SettingsBooleanControlBehavior],
 
   properties: {
diff --git a/chrome/browser/resources/settings/downloads_page/downloads_page.js b/chrome/browser/resources/settings/downloads_page/downloads_page.js
index 970ae14..7bee041 100644
--- a/chrome/browser/resources/settings/downloads_page/downloads_page.js
+++ b/chrome/browser/resources/settings/downloads_page/downloads_page.js
@@ -10,8 +10,8 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
-import '../controls/controlled_button.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/controlled_button.js';
+import '../controls/settings_toggle_button.js';
 import '../settings_shared_css.m.js';
 
 import {listenOnce} from 'chrome://resources/js/util.m.js';
diff --git a/chrome/browser/resources/settings/languages_page/languages.js b/chrome/browser/resources/settings/languages_page/languages.js
index 1f329c3..abc0771 100644
--- a/chrome/browser/resources/settings/languages_page/languages.js
+++ b/chrome/browser/resources/settings/languages_page/languages.js
@@ -41,6 +41,20 @@
     'intl.accept_languages';
 
 /**
+ * @typedef {{
+ *   initialized: boolean,
+ *   supportedLanguages: !Array<!chrome.languageSettingsPrivate.Language>,
+ *   translateTarget: string,
+ *   alwaysTranslateCodes: !Array<string>,
+ *   startingUILanguage: string,
+ *   supportedInputMethods:
+ * (!Array<!chrome.languageSettingsPrivate.InputMethod>|undefined),
+ *   currentInputMethodId: (string|undefined)
+ * }}
+ */
+let ModelArgs;
+
+/**
  * Singleton element that generates the languages model on start-up and
  * updates it whenever Chrome's pref store and other settings change.
  * @implements {LanguageHelper}
@@ -206,37 +220,58 @@
 
     const promises = [];
 
+    /**
+     * An object passed into createModel to keep track of platform-specific
+     * arguments, populated by the "promises" array.
+     * @type {!ModelArgs}
+     */
+    const args = {
+      initialized: false,
+      supportedLanguages: [],
+      translateTarget: '',
+      alwaysTranslateCodes: [],
+      startingUILanguage: '',
+
+      // Only used by ChromeOS
+      supportedInputMethods: [],
+      currentInputMethodId: '',
+    };
+
     // Wait until prefs are initialized before creating the model, so we can
     // include information about enabled languages.
-    promises[0] = CrSettingsPrefs.initialized;
+    promises.push(
+        CrSettingsPrefs.initialized.then(result => args.initialized = result));
 
     // Get the language list.
-    promises[1] = new Promise(resolve => {
-      this.languageSettingsPrivate_.getLanguageList(resolve);
-    });
+    promises.push(new Promise(resolve => {
+                    this.languageSettingsPrivate_.getLanguageList(resolve);
+                  }).then(result => args.supportedLanguages = result));
 
     // Get the translate target language.
-    promises[2] = new Promise(resolve => {
-      this.languageSettingsPrivate_.getTranslateTargetLanguage(resolve);
-    });
+    promises.push(new Promise(resolve => {
+                    this.languageSettingsPrivate_.getTranslateTargetLanguage(
+                        resolve);
+                  }).then(result => args.translateTarget = result));
 
     if (cr.isChromeOS) {
-      promises[3] = new Promise(resolve => {
-        this.languageSettingsPrivate_.getInputMethodLists(function(lists) {
-          resolve(lists.componentExtensionImes.concat(
-              lists.thirdPartyExtensionImes));
-        });
-      });
+      promises.push(
+          new Promise(resolve => {
+            this.languageSettingsPrivate_.getInputMethodLists(function(lists) {
+              resolve(lists.componentExtensionImes.concat(
+                  lists.thirdPartyExtensionImes));
+            });
+          }).then(result => args.supportedInputMethods = result));
 
-      promises[4] = new Promise(resolve => {
-        this.inputMethodPrivate_.getCurrentInputMethod(resolve);
-      });
+      promises.push(new Promise(resolve => {
+                      this.inputMethodPrivate_.getCurrentInputMethod(resolve);
+                    }).then(result => args.currentInputMethodId = result));
     }
 
     // Get the list of language-codes to always translate.
-    promises[5] = new Promise(resolve => {
-      this.languageSettingsPrivate_.getAlwaysTranslateLanguages(resolve);
-    });
+    promises.push(new Promise(resolve => {
+                    this.languageSettingsPrivate_.getAlwaysTranslateLanguages(
+                        resolve);
+                  }).then(result => args.alwaysTranslateCodes = result));
 
     if (cr.isWindows || cr.isChromeOS) {
       // Fetch the starting UI language, which affects which actions should be
@@ -255,10 +290,7 @@
         return;
       }
 
-      // TODO(dpapad): Cleanup this code. It uses results[3] and results[4]
-      // which only exist for ChromeOS.
-      this.createModel_(
-          results[1], results[2], results[3], results[4], results[5]);
+      this.createModel_(args);
 
       // <if expr="not is_macosx">
       this.boundOnSpellcheckDictionariesChanged_ =
@@ -498,24 +530,14 @@
 
   /**
    * Constructs the languages model.
-   * @param {!Array<!chrome.languageSettingsPrivate.Language>}
-   *     supportedLanguages
-   * @param {string} translateTarget Language code of the default translate
-   *     target language.
-   * @param {!Array<!chrome.languageSettingsPrivate.InputMethod>|undefined}
-   *     supportedInputMethods Input methods (Chrome OS only).
-   * @param {string|undefined} currentInputMethodId ID of the currently used
-   *     input method (Chrome OS only).
-   * @param {!Array<string>} alwaysTranslateCodes Language codes of languages
-   *     that should always automatically translate.
+   * @param {!ModelArgs} args used to populate the model
+   *     above.
    * @private
    */
-  createModel_(
-      supportedLanguages, translateTarget, supportedInputMethods,
-      currentInputMethodId, alwaysTranslateCodes) {
+  createModel_(args) {
     // Populate the hash map of supported languages.
-    for (let i = 0; i < supportedLanguages.length; i++) {
-      const language = supportedLanguages[i];
+    for (let i = 0; i < args.supportedLanguages.length; i++) {
+      const language = args.supportedLanguages[i];
       language.supportsUI = !!language.supportsUI;
       language.supportsTranslate = !!language.supportsTranslate;
       language.supportsSpellcheck = !!language.supportsSpellcheck;
@@ -531,23 +553,23 @@
     }
 
     // Create a list of enabled languages from the supported languages.
-    const enabledLanguageStates =
-        this.getEnabledLanguageStates_(translateTarget, prospectiveUILanguage);
+    const enabledLanguageStates = this.getEnabledLanguageStates_(
+        args.translateTarget, prospectiveUILanguage);
     // Populate the hash set of enabled languages.
     for (let l = 0; l < enabledLanguageStates.length; l++) {
       this.enabledLanguageSet_.add(enabledLanguageStates[l].language.code);
     }
 
     const {on: spellCheckOnLanguages, off: spellCheckOffLanguages} =
-        this.getSpellCheckLanguages_(supportedLanguages);
+        this.getSpellCheckLanguages_(args.supportedLanguages);
 
     const alwaysTranslateLangauges =
-        alwaysTranslateCodes.map(code => this.getLanguage(code));
+        args.alwaysTranslateCodes.map(code => this.getLanguage(code));
 
     const model = /** @type {!LanguagesModel} */ ({
-      supported: supportedLanguages,
+      supported: args.supportedLanguages,
       enabled: enabledLanguageStates,
-      translateTarget: translateTarget,
+      translateTarget: args.translateTarget,
       alwaysTranslate: alwaysTranslateLangauges,
       spellCheckOnLanguages,
       spellCheckOffLanguages,
@@ -558,13 +580,13 @@
     }
 
     if (cr.isChromeOS) {
-      if (supportedInputMethods) {
-        this.createInputMethodModel_(supportedInputMethods);
+      if (args.supportedInputMethods) {
+        this.createInputMethodModel_(args.supportedInputMethods);
       }
       model.inputMethods = /** @type {!InputMethodsModel} */ ({
-        supported: supportedInputMethods,
+        supported: args.supportedInputMethods,
         enabled: this.getEnabledInputMethods_(),
-        currentId: currentInputMethodId,
+        currentId: args.currentInputMethodId,
       });
     }
 
diff --git a/chrome/browser/resources/settings/languages_page/languages_page.js b/chrome/browser/resources/settings/languages_page/languages_page.js
index ec6ef7b..8c9a0a7 100644
--- a/chrome/browser/resources/settings/languages_page/languages_page.js
+++ b/chrome/browser/resources/settings/languages_page/languages_page.js
@@ -21,9 +21,9 @@
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import './languages.m.js';
 import './languages_subpage.js';
-import '../controls/controlled_radio_button.m.js';
-import '../controls/settings_radio_group.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/controlled_radio_button.js';
+import '../controls/settings_radio_group.js';
+import '../controls/settings_toggle_button.js';
 import '../icons.m.js';
 import '../settings_page/settings_animated_pages.js';
 import '../settings_page/settings_subpage.js';
diff --git a/chrome/browser/resources/settings/languages_page/languages_subpage.js b/chrome/browser/resources/settings/languages_page/languages_subpage.js
index bab2d84..48b63500 100644
--- a/chrome/browser/resources/settings/languages_page/languages_subpage.js
+++ b/chrome/browser/resources/settings/languages_page/languages_subpage.js
@@ -21,7 +21,7 @@
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import './add_languages_dialog.js';
 import './languages.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 import '../icons.m.js';
 import '../settings_shared_css.m.js';
 import '../settings_vars_css.m.js';
diff --git a/chrome/browser/resources/settings/on_startup_page/on_startup_page.js b/chrome/browser/resources/settings/on_startup_page/on_startup_page.js
index 4269d30..c12ab7dd 100644
--- a/chrome/browser/resources/settings/on_startup_page/on_startup_page.js
+++ b/chrome/browser/resources/settings/on_startup_page/on_startup_page.js
@@ -8,9 +8,9 @@
  */
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
-import '../controls/controlled_radio_button.m.js';
-import '../controls/extension_controlled_indicator.m.js';
-import '../controls/settings_radio_group.m.js';
+import '../controls/controlled_radio_button.js';
+import '../controls/extension_controlled_indicator.js';
+import '../controls/settings_radio_group.js';
 import './startup_urls_page.js';
 import '../i18n_setup.js';
 import '../settings_shared_css.m.js';
diff --git a/chrome/browser/resources/settings/on_startup_page/startup_urls_page.js b/chrome/browser/resources/settings/on_startup_page/startup_urls_page.js
index 651cbc22..db37a640 100644
--- a/chrome/browser/resources/settings/on_startup_page/startup_urls_page.js
+++ b/chrome/browser/resources/settings/on_startup_page/startup_urls_page.js
@@ -11,7 +11,7 @@
 import 'chrome://resources/cr_elements/action_link_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
-import '../controls/extension_controlled_indicator.m.js';
+import '../controls/extension_controlled_indicator.js';
 import '../settings_shared_css.m.js';
 import './startup_url_dialog.js';
 
diff --git a/chrome/browser/resources/settings/people_page/BUILD.gn b/chrome/browser/resources/settings/people_page/BUILD.gn
index bb18d98..d961314 100644
--- a/chrome/browser/resources/settings/people_page/BUILD.gn
+++ b/chrome/browser/resources/settings/people_page/BUILD.gn
@@ -231,7 +231,7 @@
   namespace_rewrites = settings_namespace_rewrites
   migrated_imports = settings_migrated_imports
   auto_imports = settings_auto_imports + [
-                   "chrome/browser/resources/settings/router.html|Router,RouteObserverBehavior",
+                   "chrome/browser/resources/settings/router.html|Route,Router,RouteObserverBehavior",
                    "chrome/browser/resources/settings/people_page/sync_browser_proxy.html|SyncBrowserProxy,SyncBrowserProxyImpl,StatusAction,SyncStatus,SyncPrefs",
                    "ui/webui/resources/html/assert.html|assert",
                  ]
diff --git a/chrome/browser/resources/settings/people_page/import_data_dialog.js b/chrome/browser/resources/settings/people_page/import_data_dialog.js
index 656e7a7..2b344f09 100644
--- a/chrome/browser/resources/settings/people_page/import_data_dialog.js
+++ b/chrome/browser/resources/settings/people_page/import_data_dialog.js
@@ -12,7 +12,7 @@
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
 import '../controls/settings_checkbox.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 import '../icons.m.js';
 import '../settings_vars_css.m.js';
 
diff --git a/chrome/browser/resources/settings/people_page/people_page.js b/chrome/browser/resources/settings/people_page/people_page.js
index 7e395ee..a333ea11 100644
--- a/chrome/browser/resources/settings/people_page/people_page.js
+++ b/chrome/browser/resources/settings/people_page/people_page.js
@@ -15,7 +15,7 @@
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 import './sync_account_control.m.js';
 import '../icons.m.js';
 import '../settings_page/settings_animated_pages.js';
diff --git a/chrome/browser/resources/settings/people_page/sync_account_control.js b/chrome/browser/resources/settings/people_page/sync_account_control.js
index 9025b8f06..d3887089 100644
--- a/chrome/browser/resources/settings/people_page/sync_account_control.js
+++ b/chrome/browser/resources/settings/people_page/sync_account_control.js
@@ -398,7 +398,9 @@
         case settings.StatusAction.ENTER_PASSPHRASE:
         case settings.StatusAction.CONFIRM_SYNC_SETTINGS:
         default:
-          router.navigateTo(router.getRoutes().SYNC);
+          router.navigateTo(
+              /** @type {{ SYNC: !settings.Route }} */ (router.getRoutes())
+                  .SYNC);
       }
     },
 
diff --git a/chrome/browser/resources/settings/people_page/sync_controls.js b/chrome/browser/resources/settings/people_page/sync_controls.js
index 663f8cda..13062cf 100644
--- a/chrome/browser/resources/settings/people_page/sync_controls.js
+++ b/chrome/browser/resources/settings/people_page/sync_controls.js
@@ -89,7 +89,9 @@
         'sync-prefs-changed', this.handleSyncPrefsChanged_.bind(this));
 
     const router = settings.Router.getInstance();
-    if (router.getCurrentRoute() === router.getRoutes().SYNC_ADVANCED) {
+    if (router.getCurrentRoute() ===
+        /** @type {{ SYNC_ADVANCED: !settings.Route }} */
+        (router.getRoutes()).SYNC_ADVANCED) {
       this.browserProxy_.didNavigateToSyncPage();
     }
   },
@@ -205,9 +207,15 @@
   /** @private */
   syncStatusChanged_() {
     const router = settings.Router.getInstance();
-    if (router.getCurrentRoute() === router.getRoutes().SYNC_ADVANCED &&
+    const routes =
+        /**
+         * @type {{ SYNC: !settings.Route,
+         *           SYNC_ADVANCED: !settings.Route }}
+         */
+        (router.getRoutes());
+    if (router.getCurrentRoute() === routes.SYNC_ADVANCED &&
         this.syncControlsHidden_()) {
-      router.navigateTo(router.getRoutes().SYNC);
+      router.navigateTo(routes.SYNC);
     }
   },
 
diff --git a/chrome/browser/resources/settings/privacy_page/BUILD.gn b/chrome/browser/resources/settings/privacy_page/BUILD.gn
index 1626a6e..c3fa8ff3 100644
--- a/chrome/browser/resources/settings/privacy_page/BUILD.gn
+++ b/chrome/browser/resources/settings/privacy_page/BUILD.gn
@@ -37,7 +37,7 @@
     "..:metrics_browser_proxy",
     "..:route",
     "..:router.m",
-    "../controls:settings_toggle_button.m",
+    "../controls:settings_toggle_button",
     "../prefs:prefs_behavior.m",
     "../site_settings:site_list",
     "../site_settings:site_settings_prefs_browser_proxy",
@@ -83,7 +83,7 @@
 js_library("do_not_track_toggle") {
   deps = [
     "..:metrics_browser_proxy",
-    "../controls:settings_toggle_button.m",
+    "../controls:settings_toggle_button",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
@@ -94,7 +94,7 @@
     ":privacy_page_browser_proxy",
     "..:i18n_setup",
     "..:lifetime_browser_proxy.m",
-    "../controls:settings_toggle_button.m",
+    "../controls:settings_toggle_button",
     "../people_page:signout_dialog.m",
     "../people_page:sync_browser_proxy.m",
     "../prefs:prefs_behavior.m",
@@ -112,7 +112,7 @@
     "..:page_visibility",
     "..:route",
     "..:router.m",
-    "../controls:settings_toggle_button.m",
+    "../controls:settings_toggle_button",
     "../prefs:prefs_behavior.m",
     "../site_settings:constants",
     "../site_settings:site_data_details_subpage",
diff --git a/chrome/browser/resources/settings/privacy_page/cookies_page.js b/chrome/browser/resources/settings/privacy_page/cookies_page.js
index a22ff9f6..be89e6c 100644
--- a/chrome/browser/resources/settings/privacy_page/cookies_page.js
+++ b/chrome/browser/resources/settings/privacy_page/cookies_page.js
@@ -11,7 +11,7 @@
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
 import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.m.js';
 import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 import '../icons.m.js';
 import '../prefs/prefs.m.js';
 import '../settings_shared_css.m.js';
diff --git a/chrome/browser/resources/settings/privacy_page/do_not_track_toggle.js b/chrome/browser/resources/settings/privacy_page/do_not_track_toggle.js
index aceb1a0..123b4c5 100644
--- a/chrome/browser/resources/settings/privacy_page/do_not_track_toggle.js
+++ b/chrome/browser/resources/settings/privacy_page/do_not_track_toggle.js
@@ -5,7 +5,7 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import '../settings_shared_css.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 
 import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/settings/privacy_page/personalization_options.js b/chrome/browser/resources/settings/privacy_page/personalization_options.js
index d48d1ed..a928633 100644
--- a/chrome/browser/resources/settings/privacy_page/personalization_options.js
+++ b/chrome/browser/resources/settings/privacy_page/personalization_options.js
@@ -9,7 +9,7 @@
  */
 import '//resources/cr_elements/cr_button/cr_button.m.js';
 import '//resources/cr_elements/cr_toggle/cr_toggle.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 import '../people_page/signout_dialog.m.js';
 import '../prefs/prefs.m.js';
 import '../settings_shared_css.m.js';
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.js b/chrome/browser/resources/settings/privacy_page/privacy_page.js
index dadf97e..addfd5ba 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.js
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.js
@@ -12,7 +12,7 @@
 import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.m.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 import '../prefs/prefs.m.js';
 import '../site_settings/settings_category_default_radio_group.js';
 import '../settings_page/settings_animated_pages.js';
diff --git a/chrome/browser/resources/settings/privacy_page/secure_dns.js b/chrome/browser/resources/settings/privacy_page/secure_dns.js
index f490bc1b..b1d5e9c 100644
--- a/chrome/browser/resources/settings/privacy_page/secure_dns.js
+++ b/chrome/browser/resources/settings/privacy_page/secure_dns.js
@@ -17,7 +17,7 @@
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
 import 'chrome://resources/cr_elements/md_select_css.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 import '../prefs/prefs.m.js';
 import '../settings_shared_css.m.js';
 import './secure_dns_input.js';
diff --git a/chrome/browser/resources/settings/privacy_page/security_page.js b/chrome/browser/resources/settings/privacy_page/security_page.js
index 62fac235..0bf8d49 100644
--- a/chrome/browser/resources/settings/privacy_page/security_page.js
+++ b/chrome/browser/resources/settings/privacy_page/security_page.js
@@ -9,7 +9,7 @@
 import './collapse_radio_button.js';
 import './disable_safebrowsing_dialog.js';
 import './secure_dns.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 import '../icons.m.js';
 import '../prefs/prefs.m.js';
 import '../settings_shared_css.m.js';
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engine_entry.js b/chrome/browser/resources/settings/search_engines_page/search_engine_entry.js
index e6b5273e..a539c29 100644
--- a/chrome/browser/resources/settings/search_engines_page/search_engine_entry.js
+++ b/chrome/browser/resources/settings/search_engines_page/search_engine_entry.js
@@ -8,7 +8,7 @@
  */
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
 import 'chrome://resources/cr_elements/icons.m.js';
-import '../controls/extension_controlled_indicator.m.js';
+import '../controls/extension_controlled_indicator.js';
 import './search_engine_entry_css.js';
 import '../settings_shared_css.m.js';
 import '../site_favicon.js';
diff --git a/chrome/browser/resources/settings/search_page/search_page.js b/chrome/browser/resources/settings/search_page/search_page.js
index 357dc6c4..802ddf07 100644
--- a/chrome/browser/resources/settings/search_page/search_page.js
+++ b/chrome/browser/resources/settings/search_page/search_page.js
@@ -11,7 +11,7 @@
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/cr_elements/md_select_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
-import '../controls/extension_controlled_indicator.m.js';
+import '../controls/extension_controlled_indicator.js';
 import '../i18n_setup.js';
 import '../settings_page/settings_animated_pages.js';
 import '../settings_page/settings_subpage.js';
diff --git a/chrome/browser/resources/settings/settings.gni b/chrome/browser/resources/settings/settings.gni
index c620072c..2733cf5 100644
--- a/chrome/browser/resources/settings/settings.gni
+++ b/chrome/browser/resources/settings/settings.gni
@@ -61,6 +61,17 @@
 settings_migrated_imports = [
   "chrome/browser/resources/settings/about_page/about_page_browser_proxy.html",
   "chrome/browser/resources/settings/appearance_page/fonts_browser_proxy.html",
+  "chrome/browser/resources/settings/controls/controlled_button.html",
+  "chrome/browser/resources/settings/controls/controlled_radio_button.html",
+  "chrome/browser/resources/settings/controls/extension_controlled_indicator.html",
+  "chrome/browser/resources/settings/controls/password_prompt_dialog.html",
+  "chrome/browser/resources/settings/controls/pref_control_behavior.html",
+  "chrome/browser/resources/settings/controls/settings_boolean_control_behavior.html",
+  "chrome/browser/resources/settings/controls/settings_dropdown_menu.html",
+  "chrome/browser/resources/settings/controls/settings_radio_group.html",
+  "chrome/browser/resources/settings/controls/settings_slider.html",
+  "chrome/browser/resources/settings/controls/settings_textarea.html",
+  "chrome/browser/resources/settings/controls/settings_toggle_button.html",
   "chrome/browser/resources/settings/ensure_lazy_loaded.html",
   "chrome/browser/resources/settings/i18n_setup.html",
   "chrome/browser/resources/settings/privacy_page/personalization_options.html",
@@ -70,4 +81,5 @@
   "chrome/browser/resources/settings/settings_page/settings_animated_pages.html",
   "chrome/browser/resources/settings/settings_page/settings_section.html",
   "chrome/browser/resources/settings/settings_page/settings_subpage.html",
+  "ui/webui/resources/html/plural_string_proxy.html",
 ]
diff --git a/chrome/browser/resources/settings/site_settings/BUILD.gn b/chrome/browser/resources/settings/site_settings/BUILD.gn
index e7d6b85..5ab00855 100644
--- a/chrome/browser/resources/settings/site_settings/BUILD.gn
+++ b/chrome/browser/resources/settings/site_settings/BUILD.gn
@@ -174,7 +174,7 @@
   deps = [
     ":constants",
     ":site_settings_behavior",
-    "../controls:settings_radio_group.m",
+    "../controls:settings_radio_group",
     "../privacy_page:collapse_radio_button",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert.m",
diff --git a/chrome/browser/resources/settings/site_settings/category_default_setting.js b/chrome/browser/resources/settings/site_settings/category_default_setting.js
index f1aeec3e..1648f4c 100644
--- a/chrome/browser/resources/settings/site_settings/category_default_setting.js
+++ b/chrome/browser/resources/settings/site_settings/category_default_setting.js
@@ -34,7 +34,7 @@
  * TODO(crbug.com/1113642): Remove this element when content settings redesign
  * is launched.
  */
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 import '../settings_shared_css.m.js';
 
 import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
diff --git a/chrome/browser/resources/settings/site_settings/pdf_documents.js b/chrome/browser/resources/settings/site_settings/pdf_documents.js
index d47696f..9565251d 100644
--- a/chrome/browser/resources/settings/site_settings/pdf_documents.js
+++ b/chrome/browser/resources/settings/site_settings/pdf_documents.js
@@ -8,7 +8,7 @@
  * settings for viewing PDF documents under Site Settings.
  */
 
-import '../controls/settings_toggle_button.m.js';
+import '../controls/settings_toggle_button.js';
 import '../settings_shared_css.m.js';
 
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/settings/site_settings/settings_category_default_radio_group.js b/chrome/browser/resources/settings/site_settings/settings_category_default_radio_group.js
index 1c25bf3..d5f1cf8 100644
--- a/chrome/browser/resources/settings/site_settings/settings_category_default_radio_group.js
+++ b/chrome/browser/resources/settings/site_settings/settings_category_default_radio_group.js
@@ -8,7 +8,7 @@
  * a certain category under Site Settings.
  */
 import '../settings_shared_css.m.js';
-import '../controls/settings_radio_group.m.js';
+import '../controls/settings_radio_group.js';
 import '../privacy_page/collapse_radio_button.js';
 
 import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
diff --git a/chrome/browser/resources/settings/system_page/system_page.js b/chrome/browser/resources/settings/system_page/system_page.js
index 74350e0..22a379e 100644
--- a/chrome/browser/resources/settings/system_page/system_page.js
+++ b/chrome/browser/resources/settings/system_page/system_page.js
@@ -11,8 +11,8 @@
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
 import 'chrome://resources/cr_elements/policy/cr_policy_pref_indicator.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
-import '../controls/extension_controlled_indicator.m.js';
-import '../controls/settings_toggle_button.m.js';
+import '../controls/extension_controlled_indicator.js';
+import '../controls/settings_toggle_button.js';
 import '../prefs/prefs.m.js';
 import '../settings_shared_css.m.js';
 
diff --git a/chrome/browser/resources/tab_search/app.js b/chrome/browser/resources/tab_search/app.js
index dad2532b..3012a7e 100644
--- a/chrome/browser/resources/tab_search/app.js
+++ b/chrome/browser/resources/tab_search/app.js
@@ -492,7 +492,8 @@
    * @private
    */
   ariaLabel_(tabData) {
-    return `${tabData.tab.title} ${tabData.hostname}`;
+    return `${tabData.tab.title} ${tabData.hostname}
+        ${tabData.tab.lastActiveElapsedText}`;
   }
 
   /**
diff --git a/chrome/browser/resources/tab_search/tab_search_item.html b/chrome/browser/resources/tab_search/tab_search_item.html
index ddb2e0e..985e165 100644
--- a/chrome/browser/resources/tab_search/tab_search_item.html
+++ b/chrome/browser/resources/tab_search/tab_search_item.html
@@ -58,11 +58,27 @@
     margin-bottom: 3px;
   }
 
-  #secondaryText {
+  #secondaryContainer {
     color: var(--cr-secondary-text-color);
+    display: flex;
     font-size: var(--mwb-secondary-text-font-size);
   }
 
+  #secondaryTimestamp {
+    flex-shrink: 0;
+  }
+
+  #secondaryTextAriaLabel {
+    clip: rect(0,0,0,0);
+    display: inline-block;
+    position: fixed;
+  }
+
+  #separator {
+    margin-inline-end: 4px;
+    margin-inline-start: 4px;
+  }
+
   cr-icon-button {
     --cr-icon-button-icon-size: var(--mwb-icon-size);
     --cr-icon-button-margin-end: 0;
@@ -78,7 +94,14 @@
 <div class="favicon" style="background-image:[[faviconUrl_(data.tab)]]"></div>
 <div class="text-container">
   <div id="primaryText" title="[[data.tab.title]]"></div>
-  <div id="secondaryText"></div>
+  <div id="secondaryTextAriaLabel" title="[[secondaryTextAriaLabel_]]"></div>
+  <div id="secondaryContainer" aria-hidden="true">
+    <div id="secondaryText"></div>
+    <div id="separator">•</div>
+    <div id="secondaryTimestamp">
+      [[data.tab.lastActiveElapsedText]]
+    </div>
+  </div>
 </div>
 <div class="button-container">
   <cr-icon-button id="closeButton" aria-label="[[ariaLabel_(data.tab.title)]]"
diff --git a/chrome/browser/resources/tab_search/tab_search_item.js b/chrome/browser/resources/tab_search/tab_search_item.js
index 922df1b..f9fa81d 100644
--- a/chrome/browser/resources/tab_search/tab_search_item.js
+++ b/chrome/browser/resources/tab_search/tab_search_item.js
@@ -42,6 +42,12 @@
 
       /** @type {number} */
       index: Number,
+
+      /** @private {string} */
+      secondaryTextAriaLabel_: {
+        type: String,
+        value: '',
+      },
     };
   }
 
@@ -78,10 +84,15 @@
         this.data.hostnameHighlightRanges);
 
     // Show chrome:// if it's a chrome internal url
+    let secondaryLabel = this.data.hostname;
     if (new URL(this.data.tab.url).protocol === 'chrome:') {
       /** @type {!HTMLElement} */ (this.$.secondaryText)
           .prepend(document.createTextNode('chrome://'));
+      secondaryLabel = `chrome://${secondaryLabel}`;
     }
+
+    this.secondaryTextAriaLabel_ =
+        `${secondaryLabel} ${this.data.tab.lastActiveElapsedText}`;
   }
 
   /**
diff --git a/chrome/browser/resources/tab_strip/BUILD.gn b/chrome/browser/resources/tab_strip/BUILD.gn
index e51d545..f03452f 100644
--- a/chrome/browser/resources/tab_strip/BUILD.gn
+++ b/chrome/browser/resources/tab_strip/BUILD.gn
@@ -46,7 +46,6 @@
   out_folder = "$target_gen_dir/$preprocess_folder"
   out_manifest = "$target_gen_dir/$preprocess_manifest"
   in_files = [
-    "custom_element.js",
     "drag_manager.js",
     "tab_strip_embedder_proxy.js",
     "tab_swiper.js",
@@ -89,7 +88,6 @@
   deps = [
     ":alert_indicator",
     ":alert_indicators",
-    ":custom_element",
     ":drag_manager",
     ":tab",
     ":tab_group",
@@ -102,8 +100,8 @@
 
 js_library("alert_indicator") {
   deps = [
-    ":custom_element",
     ":tabs_api_proxy",
+    "//ui/webui/resources/js:custom_element",
     "//ui/webui/resources/js:load_time_data.m",
   ]
 }
@@ -111,14 +109,11 @@
 js_library("alert_indicators") {
   deps = [
     ":alert_indicator",
-    ":custom_element",
     ":tabs_api_proxy",
+    "//ui/webui/resources/js:custom_element",
   ]
 }
 
-js_library("custom_element") {
-}
-
 js_library("drag_manager") {
   deps = [
     ":tab",
@@ -140,10 +135,10 @@
 js_library("tab") {
   deps = [
     ":alert_indicators",
-    ":custom_element",
     ":tab_strip_embedder_proxy",
     ":tab_swiper",
     ":tabs_api_proxy",
+    "//ui/webui/resources/js:custom_element",
     "//ui/webui/resources/js:icon.m",
     "//ui/webui/resources/js:load_time_data.m",
     "//ui/webui/resources/js:util.m",
@@ -153,22 +148,22 @@
 
 js_library("tab_group") {
   deps = [
-    ":custom_element",
     ":tab_strip_embedder_proxy",
     ":tabs_api_proxy",
+    "//ui/webui/resources/js:custom_element",
     "//ui/webui/resources/js:load_time_data.m",
   ]
 }
 
 js_library("tab_list") {
   deps = [
-    ":custom_element",
     ":drag_manager",
     ":tab",
     ":tab_group",
     ":tab_strip_embedder_proxy",
     ":tabs_api_proxy",
     "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:custom_element",
     "//ui/webui/resources/js:event_tracker.m",
     "//ui/webui/resources/js:load_time_data.m",
     "//ui/webui/resources/js:util.m",
diff --git a/chrome/browser/resources/tab_strip/alert_indicator.js b/chrome/browser/resources/tab_strip/alert_indicator.js
index 480b42812..966b005f 100644
--- a/chrome/browser/resources/tab_strip/alert_indicator.js
+++ b/chrome/browser/resources/tab_strip/alert_indicator.js
@@ -3,9 +3,10 @@
 // found in the LICENSE file.
 
 import './strings.m.js';
+
+import {CustomElement} from 'chrome://resources/js/custom_element.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
-import {CustomElement} from './custom_element.js';
 import {TabAlertState} from './tabs_api_proxy.js';
 
 /** @const {string} */
diff --git a/chrome/browser/resources/tab_strip/alert_indicators.js b/chrome/browser/resources/tab_strip/alert_indicators.js
index eec52526..a60d8b6 100644
--- a/chrome/browser/resources/tab_strip/alert_indicators.js
+++ b/chrome/browser/resources/tab_strip/alert_indicators.js
@@ -2,8 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {CustomElement} from 'chrome://resources/js/custom_element.js';
+
 import {AlertIndicatorElement} from './alert_indicator.js';
-import {CustomElement} from './custom_element.js';
 import {TabAlertState} from './tabs_api_proxy.js';
 
 export class AlertIndicatorsElement extends CustomElement {
diff --git a/chrome/browser/resources/tab_strip/tab.js b/chrome/browser/resources/tab_strip/tab.js
index 84b0f047..85ebdd0b 100644
--- a/chrome/browser/resources/tab_strip/tab.js
+++ b/chrome/browser/resources/tab_strip/tab.js
@@ -5,12 +5,12 @@
 import './strings.m.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
+import {CustomElement} from 'chrome://resources/js/custom_element.js';
 import {getFavicon} from 'chrome://resources/js/icon.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {isRTL} from 'chrome://resources/js/util.m.js';
 
 import {AlertIndicatorsElement} from './alert_indicators.js';
-import {CustomElement} from './custom_element.js';
 import {TabStripEmbedderProxy, TabStripEmbedderProxyImpl} from './tab_strip_embedder_proxy.js';
 import {TabSwiper} from './tab_swiper.js';
 import {CloseTabAction, TabData, TabNetworkState, TabsApiProxy, TabsApiProxyImpl} from './tabs_api_proxy.js';
diff --git a/chrome/browser/resources/tab_strip/tab_group.js b/chrome/browser/resources/tab_strip/tab_group.js
index 33150b9..9fe4a80 100644
--- a/chrome/browser/resources/tab_strip/tab_group.js
+++ b/chrome/browser/resources/tab_strip/tab_group.js
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {CustomElement} from 'chrome://resources/js/custom_element.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
-import {CustomElement} from './custom_element.js';
 import {TabStripEmbedderProxy, TabStripEmbedderProxyImpl} from './tab_strip_embedder_proxy.js';
 import {TabGroupVisualData} from './tabs_api_proxy.js';
 
diff --git a/chrome/browser/resources/tab_strip/tab_list.js b/chrome/browser/resources/tab_strip/tab_list.js
index 62487515..f6df4e0 100644
--- a/chrome/browser/resources/tab_strip/tab_list.js
+++ b/chrome/browser/resources/tab_strip/tab_list.js
@@ -10,10 +10,10 @@
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {addWebUIListener, removeWebUIListener, WebUIListener} from 'chrome://resources/js/cr.m.js';
 import {FocusOutlineManager} from 'chrome://resources/js/cr/ui/focus_outline_manager.m.js';
+import {CustomElement} from 'chrome://resources/js/custom_element.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
 import {isRTL} from 'chrome://resources/js/util.m.js';
 
-import {CustomElement} from './custom_element.js';
 import {DragManager, DragManagerDelegate} from './drag_manager.js';
 import {isTabElement, TabElement} from './tab.js';
 import {isTabGroupElement, TabGroupElement} from './tab_group.js';
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request.cc b/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request.cc
index 38fd303..5f42bb2 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request.h"
 
+#include "base/files/memory_mapped_file.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/task/post_task.h"
 #include "chrome/browser/file_util_service.h"
@@ -20,73 +21,47 @@
 namespace {
 
 std::pair<BinaryUploadService::Result, BinaryUploadService::Request::Data>
-GetFileContentsForLargeFile(const base::FilePath& path, base::File* file) {
-  size_t file_size = file->GetLength();
-  BinaryUploadService::Request::Data file_data;
-  file_data.size = file_size;
-
-  // Only read 50MB at a time to avoid having very large files in memory.
-  std::unique_ptr<crypto::SecureHash> secure_hash =
-      crypto::SecureHash::Create(crypto::SecureHash::SHA256);
-  size_t bytes_read = 0;
-  std::string buf;
-  buf.reserve(BinaryUploadService::kMaxUploadSizeBytes);
-  while (bytes_read < file_size) {
-    int64_t bytes_currently_read = file->ReadAtCurrentPos(
-        &buf[0], BinaryUploadService::kMaxUploadSizeBytes);
-
-    if (bytes_currently_read == -1) {
-      return std::make_pair(BinaryUploadService::Result::UNKNOWN,
-                            BinaryUploadService::Request::Data());
-    }
-
-    secure_hash->Update(buf.data(), bytes_currently_read);
-
-    bytes_read += bytes_currently_read;
-  }
-
-  file_data.hash.resize(crypto::kSHA256Length);
-  secure_hash->Finish(base::data(file_data.hash), crypto::kSHA256Length);
-  file_data.hash =
-      base::HexEncode(base::as_bytes(base::make_span(file_data.hash)));
-  return std::make_pair(BinaryUploadService::Result::FILE_TOO_LARGE, file_data);
-}
-
-std::pair<BinaryUploadService::Result, BinaryUploadService::Request::Data>
-GetFileContentsForNormalFile(const base::FilePath& path, base::File* file) {
-  size_t file_size = file->GetLength();
-  BinaryUploadService::Request::Data file_data;
-  file_data.size = file_size;
-  file_data.contents.resize(file_size);
-
-  int64_t bytes_currently_read =
-      file->ReadAtCurrentPos(&file_data.contents[0], file_size);
-
-  if (bytes_currently_read == -1) {
-    return std::make_pair(BinaryUploadService::Result::UNKNOWN,
-                          BinaryUploadService::Request::Data());
-  }
-
-  DCHECK_EQ(static_cast<size_t>(bytes_currently_read), file_size);
-
-  file_data.hash = crypto::SHA256HashString(file_data.contents);
-  file_data.hash =
-      base::HexEncode(base::as_bytes(base::make_span(file_data.hash)));
-  return std::make_pair(BinaryUploadService::Result::SUCCESS, file_data);
-}
-
-std::pair<BinaryUploadService::Result, BinaryUploadService::Request::Data>
 GetFileDataBlocking(const base::FilePath& path) {
   base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+
   if (!file.IsValid()) {
     return std::make_pair(BinaryUploadService::Result::UNKNOWN,
                           BinaryUploadService::Request::Data());
   }
 
-  return static_cast<size_t>(file.GetLength()) >
-                 BinaryUploadService::kMaxUploadSizeBytes
-             ? GetFileContentsForLargeFile(path, &file)
-             : GetFileContentsForNormalFile(path, &file);
+  size_t file_size = file.GetLength();
+  if (file_size == 0) {
+    return std::make_pair(BinaryUploadService::Result::SUCCESS,
+                          BinaryUploadService::Request::Data());
+  }
+
+  base::MemoryMappedFile mm_file;
+  if (!mm_file.Initialize(std::move(file)) || !mm_file.IsValid()) {
+    return std::make_pair(BinaryUploadService::Result::UNKNOWN,
+                          BinaryUploadService::Request::Data());
+  }
+
+  BinaryUploadService::Result result;
+  BinaryUploadService::Request::Data file_data;
+  file_data.size = file_size;
+
+  if (file_size <= BinaryUploadService::kMaxUploadSizeBytes) {
+    result = BinaryUploadService::Result::SUCCESS;
+    file_data.contents =
+        std::string(reinterpret_cast<char*>(mm_file.data()), file_size);
+  } else {
+    result = BinaryUploadService::Result::FILE_TOO_LARGE;
+  }
+
+  std::unique_ptr<crypto::SecureHash> secure_hash =
+      crypto::SecureHash::Create(crypto::SecureHash::SHA256);
+  secure_hash->Update(mm_file.data(), file_size);
+  file_data.hash.resize(crypto::kSHA256Length);
+  secure_hash->Finish(base::data(file_data.hash), crypto::kSHA256Length);
+  file_data.hash =
+      base::HexEncode(base::as_bytes(base::make_span(file_data.hash)));
+
+  return std::make_pair(result, file_data);
 }
 
 }  // namespace
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request_unittest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request_unittest.cc
index 3dee3e16..0c8c247 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request_unittest.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request_unittest.cc
@@ -131,6 +131,31 @@
 
     EXPECT_TRUE(called);
   }
+
+  {
+    // Empty files should return SUCCESS as they have no content to scan.
+    base::FilePath path = temp_dir.GetPath().AppendASCII("empty.doc");
+    base::File file(path, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+    auto request =
+        MakeRequest(/*block_unsupported_types=*/false, path, path.BaseName());
+
+    bool called = false;
+    base::RunLoop run_loop;
+    request->GetRequestData(base::BindLambdaForTesting(
+        [&run_loop, &called](BinaryUploadService::Result result,
+                             const BinaryUploadService::Request::Data& data) {
+          called = true;
+          run_loop.Quit();
+
+          EXPECT_EQ(result, BinaryUploadService::Result::SUCCESS);
+          EXPECT_EQ(data.size, 0u);
+          EXPECT_TRUE(data.contents.empty());
+          EXPECT_TRUE(data.hash.empty());
+        }));
+    run_loop.Run();
+
+    EXPECT_TRUE(called);
+  }
 }
 
 TEST_F(FileAnalysisRequestTest, NormalFiles) {
diff --git a/chrome/browser/search/instant_service.cc b/chrome/browser/search/instant_service.cc
index 9357ef1..1c2644b4 100644
--- a/chrome/browser/search/instant_service.cc
+++ b/chrome/browser/search/instant_service.cc
@@ -228,9 +228,7 @@
   background_service_ = NtpBackgroundServiceFactory::GetForProfile(profile_);
 
   // Listen for theme installation.
-  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-                 content::Source<ThemeService>(
-                     ThemeServiceFactory::GetForProfile(profile_)));
+  ThemeServiceFactory::GetForProfile(profile_)->AddObserver(this);
 
   // Set up the data sources that Instant uses on the NTP.
   content::URLDataSource::Add(profile_,
@@ -287,6 +285,11 @@
   }
 }
 
+void InstantService::OnThemeChanged() {
+  theme_ = nullptr;
+  UpdateNtpTheme();
+}
+
 void InstantService::DeleteMostVisitedItem(const GURL& url) {
   if (most_visited_sites_) {
     most_visited_sites_->AddOrRemoveBlockedUrl(url, true);
@@ -515,6 +518,8 @@
   if (most_visited_sites_) {
     most_visited_sites_.reset();
   }
+
+  ThemeServiceFactory::GetForProfile(profile_)->RemoveObserver(this);
 }
 
 void InstantService::OnNextCollectionImageAvailable() {
@@ -559,10 +564,6 @@
         OnRendererProcessTerminated(rph->GetID());
       break;
     }
-    case chrome::NOTIFICATION_BROWSER_THEME_CHANGED:
-      theme_ = nullptr;
-      UpdateNtpTheme();
-      break;
     default:
       NOTREACHED() << "Unexpected notification type in InstantService.";
   }
diff --git a/chrome/browser/search/instant_service.h b/chrome/browser/search/instant_service.h
index f8e7839..50bdac4 100644
--- a/chrome/browser/search/instant_service.h
+++ b/chrome/browser/search/instant_service.h
@@ -20,6 +20,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/search/background/ntp_background_service.h"
 #include "chrome/browser/search/background/ntp_background_service_observer.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/image_fetcher/core/image_fetcher_impl.h"
 #include "components/keyed_service/core/keyed_service.h"
@@ -63,7 +64,8 @@
                        public NtpBackgroundServiceObserver,
                        public content::NotificationObserver,
                        public ntp_tiles::MostVisitedSites::Observer,
-                       public ui::NativeThemeObserver {
+                       public ui::NativeThemeObserver,
+                       public ThemeServiceObserver {
  public:
   explicit InstantService(Profile* profile);
   ~InstantService() override;
@@ -96,6 +98,9 @@
   // items.
   void OnNewTabPageOpened();
 
+  // ThemeServiceObserver implementation.
+  void OnThemeChanged() override;
+
   // Most visited item APIs.
   //
   // Invoked when the Instant page wants to delete a Most Visited item.
diff --git a/chrome/browser/search/instant_service_unittest.cc b/chrome/browser/search/instant_service_unittest.cc
index 422034d..29818448 100644
--- a/chrome/browser/search/instant_service_unittest.cc
+++ b/chrome/browser/search/instant_service_unittest.cc
@@ -10,10 +10,10 @@
 #include "base/path_service.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/test/mock_callback.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/search/background/ntp_background_service.h"
 #include "chrome/browser/search/instant_service_observer.h"
 #include "chrome/browser/search/instant_unittest_base.h"
+#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
@@ -792,11 +792,9 @@
   // Install colors, theme update should trigger SetNTPElementsNtpTheme() and
   // update NTP themed elements info.
   ThemeService* theme_service = ThemeServiceFactory::GetForProfile(profile());
-  content::WindowedNotificationObserver theme_change_observer(
-      chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-      content::Source<ThemeService>(theme_service));
+  test::ThemeServiceChangedWaiter waiter(theme_service);
   theme_service->BuildAutogeneratedThemeFromColor(SK_ColorRED);
-  theme_change_observer.Wait();
+  waiter.WaitForThemeChanged();
 
   theme = instant_service_->GetInitializedNtpTheme();
   EXPECT_NE(default_text_color, theme->text_color);
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextBridge.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextBridge.java
index 0afe1940..4df9acfb 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextBridge.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextBridge.java
@@ -31,10 +31,6 @@
         LinkToTextBridgeJni.get().logGenerateErrorBlockList();
     }
 
-    public static void logGenerateErrorTimeout() {
-        LinkToTextBridgeJni.get().logGenerateErrorTimeout();
-    }
-
     // TODO(gayane): Update the name whenever |shared_highlighting::ShouldOfferLinkToText| updated
     // to moredescriptive name.
     public static boolean shouldOfferLinkToText(GURL url) {
@@ -48,7 +44,6 @@
         void logGenerateErrorTabCrash();
         void logGenerateErrorIFrame();
         void logGenerateErrorBlockList();
-        void logGenerateErrorTimeout();
         boolean shouldOfferLinkToText(GURL url);
     }
 }
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinator.java
index 960e0f87..0d51a83b 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinator.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinator.java
@@ -10,7 +10,6 @@
 import androidx.annotation.IntDef;
 
 import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.base.task.PostTask;
 import org.chromium.blink.mojom.TextFragmentSelectorProducer;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -20,7 +19,6 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabHidingType;
 import org.chromium.components.browser_ui.share.ShareParams;
-import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.widget.Toast;
 import org.chromium.url.GURL;
 
@@ -38,7 +36,6 @@
     private static final String SHARE_TEXT_TEMPLATE = "\"%s\"\n";
     private static final String TEXT_FRAGMENT_PREFIX = ":~:text=";
     private static final String INVALID_SELECTOR = "";
-    private static final long TIMEOUT_MS = 50;
     private final Context mContext;
     private final ChromeOptionShareCallback mChromeOptionShareCallback;
     private final String mVisibleUrl;
@@ -88,7 +85,6 @@
 
         mRequestSelectorStartTime = System.currentTimeMillis();
         requestSelector();
-        PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, () -> timeout(), TIMEOUT_MS);
     }
 
     public ShareParams getShareParams(@LinkGeneration int linkGeneration) {
@@ -106,7 +102,7 @@
                 System.currentTimeMillis() - mRequestSelectorStartTime);
         if (mCancelRequest) return;
 
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION)) {
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.PREEMTIVE_LINK_TO_TEXT_GENERATION)) {
             mShareLinkParams = selector.isEmpty()
                     ? null
                     : new ShareParams
@@ -164,7 +160,7 @@
 
         mProducer = mTab.getWebContents().getMainFrame().getInterfaceToRendererFrame(
                 TextFragmentSelectorProducer.MANAGER);
-        mProducer.requestSelector(new TextFragmentSelectorProducer.RequestSelectorResponse() {
+        mProducer.generateSelector(new TextFragmentSelectorProducer.GenerateSelectorResponse() {
             @Override
             public void call(String selector) {
                 onSelectorReady(selector);
@@ -204,18 +200,9 @@
     }
 
     private void cleanup() {
-        if (mProducer != null) {
-            mProducer.cancel();
-            mProducer.close();
-        }
+        // TODO(gayane): Consider canceling request in renderer.
+        if (mProducer != null) mProducer.close();
         mCancelRequest = true;
         mTab.removeObserver(this);
     }
-
-    private void timeout() {
-        if (!mCancelRequest) {
-            LinkToTextBridge.logGenerateErrorTimeout();
-            onSelectorReady(INVALID_SELECTOR);
-        }
-    }
 }
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
index bc0ca13..10afeb10 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
@@ -238,7 +238,7 @@
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_SHARING_HUB_V15)
                 && ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_SHARE_HIGHLIGHTS_ANDROID)
                 && !ChromeFeatureList.isEnabled(
-                        ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION)) {
+                        ChromeFeatureList.PREEMTIVE_LINK_TO_TEXT_GENERATION)) {
             mOrderedFirstPartyOptions.add(createHighlightsFirstPartyOption());
         }
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_SHARE_QRCODE)
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java
index 377effa7..eb717f4 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java
@@ -227,7 +227,7 @@
             fetchFavicon(mParams.getUrl());
         }
 
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION)
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.PREEMTIVE_LINK_TO_TEXT_GENERATION)
                 && contentTypes.contains(ContentType.HIGHLIGHTED_TEXT)) {
             setLinkImageViewForPreview();
         }
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java
index 9d18d5d..412c49a 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java
@@ -169,7 +169,7 @@
      * @param state The state from {@link LinkGeneration} to which ShareParams should be updated.
      */
     void updateShareSheetForLinkToText(@LinkGeneration int state) {
-        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION)
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.PREEMTIVE_LINK_TO_TEXT_GENERATION)
                 || mLinkToTextCoordinator == null) {
             return;
         }
@@ -202,7 +202,7 @@
      */
     public void showInitialShareSheet(
             ShareParams params, ChromeShareExtras chromeShareExtras, long shareStartTime) {
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION)
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.PREEMTIVE_LINK_TO_TEXT_GENERATION)
                 && chromeShareExtras.isUserHighlightedText()) {
             String tabUrl =
                     mTabProvider.get().isInitialized() ? mTabProvider.get().getUrl().getSpec() : "";
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinatorTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinatorTest.java
index c7f478d..57f9c87 100644
--- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinatorTest.java
+++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinatorTest.java
@@ -131,7 +131,7 @@
 
     @Test
     @SmallTest
-    @Features.DisableFeatures({ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION})
+    @Features.DisableFeatures({ChromeFeatureList.PREEMTIVE_LINK_TO_TEXT_GENERATION})
     public void onSelectorReadyTest() {
         MockLinkToTextCoordinator coordinator = new MockLinkToTextCoordinator(
                 mAcivity, mTab, mShareCallback, VISIBLE_URL, SELECTED_TEXT);
@@ -142,7 +142,7 @@
 
     @Test
     @SmallTest
-    @Features.DisableFeatures({ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION})
+    @Features.DisableFeatures({ChromeFeatureList.PREEMTIVE_LINK_TO_TEXT_GENERATION})
     public void onSelectorReadyTest_EmptySelector() {
         MockLinkToTextCoordinator coordinator = new MockLinkToTextCoordinator(
                 mAcivity, mTab, mShareCallback, VISIBLE_URL, SELECTED_TEXT);
@@ -153,8 +153,8 @@
 
     @Test
     @SmallTest
-    @Features.EnableFeatures({ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION})
-    public void onSelectorReadyTest_PreemptiveLinkToTextGeneration() {
+    @Features.EnableFeatures({ChromeFeatureList.PREEMTIVE_LINK_TO_TEXT_GENERATION})
+    public void onSelectorReadyTest_PreemtiveLinkToTextGeneration() {
         MockLinkToTextCoordinator coordinator = new MockLinkToTextCoordinator(
                 mAcivity, mTab, mShareCallback, VISIBLE_URL, SELECTED_TEXT);
         // OnSelectorReady should call back the share sheet.
@@ -164,8 +164,8 @@
 
     @Test
     @SmallTest
-    @Features.EnableFeatures({ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION})
-    public void onSelectorReadyTest_EmptySelector_PreemptiveLinkToTextGeneration() {
+    @Features.EnableFeatures({ChromeFeatureList.PREEMTIVE_LINK_TO_TEXT_GENERATION})
+    public void onSelectorReadyTest_EmptySelector_PreemtiveLinkToTextGeneration() {
         MockLinkToTextCoordinator coordinator = new MockLinkToTextCoordinator(
                 mAcivity, mTab, mShareCallback, VISIBLE_URL, SELECTED_TEXT);
         // OnSelectorReady should call back the share sheet.
@@ -175,8 +175,8 @@
 
     @Test
     @SmallTest
-    @Features.EnableFeatures({ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION})
-    public void showShareSheetTest_PreemptiveLinkToTextGeneration_LinkGeneration() {
+    @Features.EnableFeatures({ChromeFeatureList.PREEMTIVE_LINK_TO_TEXT_GENERATION})
+    public void showShareSheetTest_PreemtiveLinkToTextGeneration_LinkGeneration() {
         ShareParams shareParams = new ShareParams.Builder(/*window=*/null, "", VISIBLE_URL)
                                           .setText(SELECTED_TEXT)
                                           .build();
@@ -189,8 +189,8 @@
 
     @Test
     @SmallTest
-    @Features.EnableFeatures({ChromeFeatureList.PREEMPTIVE_LINK_TO_TEXT_GENERATION})
-    public void showShareSheetTest_EmptySelector_PreemptiveLinkToTextGeneration() {
+    @Features.EnableFeatures({ChromeFeatureList.PREEMTIVE_LINK_TO_TEXT_GENERATION})
+    public void showShareSheetTest_EmptySelector_PreemtiveLinkToTextGeneration() {
         ShareParams shareParams = new ShareParams.Builder(/*window=*/null, "", VISIBLE_URL)
                                           .setText(SELECTED_TEXT)
                                           .build();
diff --git a/chrome/browser/share/link_to_text_bridge.cc b/chrome/browser/share/link_to_text_bridge.cc
index 7e7e96c8..0f66c76 100644
--- a/chrome/browser/share/link_to_text_bridge.cc
+++ b/chrome/browser/share/link_to_text_bridge.cc
@@ -30,10 +30,6 @@
   shared_highlighting::LogGenerateErrorBlockList();
 }
 
-static void JNI_LinkToTextBridge_LogGenerateErrorTimeout(JNIEnv* env) {
-  shared_highlighting::LogGenerateErrorTimeout();
-}
-
 // TODO(gayane): Update the name whenever
 // |shared_highlighting::ShouldOfferLinkToText| updated to more descriptive
 // name.
diff --git a/chrome/browser/signin/signin_promo.cc b/chrome/browser/signin/signin_promo.cc
index 444319b23..7ae725c 100644
--- a/chrome/browser/signin/signin_promo.cc
+++ b/chrome/browser/signin/signin_promo.cc
@@ -94,7 +94,8 @@
 content::StoragePartition* GetSigninPartition(
     content::BrowserContext* browser_context) {
   const auto signin_partition_config = content::StoragePartitionConfig::Create(
-      "chrome-signin", /* partition_name= */ "", /* in_memory= */ true);
+      browser_context, "chrome-signin", /* partition_name= */ "",
+      /* in_memory= */ true);
   return content::BrowserContext::GetStoragePartition(browser_context,
                                                       signin_partition_config);
 }
diff --git a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetCoordinator.java b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetCoordinator.java
index 54727f7..c4d0e48 100644
--- a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetCoordinator.java
+++ b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetCoordinator.java
@@ -37,21 +37,12 @@
         public void onSheetClosed(@StateChangeReason int reason) {
             super.onSheetClosed(reason);
             if (reason == StateChangeReason.SWIPE) {
-                SigninMetricsUtils.logAccountConsistencyPromoAction(
-                        AccountConsistencyPromoAction.DISMISSED_SWIPE_DOWN);
+                logOnDismissMetrics(AccountConsistencyPromoAction.DISMISSED_SWIPE_DOWN);
             } else if (reason == StateChangeReason.BACK_PRESS) {
-                SigninMetricsUtils.logAccountConsistencyPromoAction(
-                        AccountConsistencyPromoAction.DISMISSED_BACK);
+                logOnDismissMetrics(AccountConsistencyPromoAction.DISMISSED_BACK);
             } else if (reason == StateChangeReason.TAP_SCRIM) {
-                SigninMetricsUtils.logAccountConsistencyPromoAction(
-                        AccountConsistencyPromoAction.DISMISSED_SCRIM);
-            } else {
-                // Return for other dismiss cases so we don't record web signin metrics for them.
-                return;
+                logOnDismissMetrics(AccountConsistencyPromoAction.DISMISSED_SCRIM);
             }
-            SigninPreferencesManager.getInstance()
-                    .incrementAccountPickerBottomSheetActiveDismissalCount();
-            SigninMetricsUtils.logWebSignin();
         }
 
         @Override
@@ -129,9 +120,18 @@
 
     @MainThread
     private void dismissBottomSheet() {
+        logOnDismissMetrics(AccountConsistencyPromoAction.DISMISSED_BUTTON);
         mBottomSheetController.hideContent(mView, true);
     }
 
+    @MainThread
+    private void logOnDismissMetrics(@AccountConsistencyPromoAction int promoAction) {
+        SigninMetricsUtils.logAccountConsistencyPromoAction(promoAction);
+        SigninPreferencesManager.getInstance()
+                .incrementAccountPickerBottomSheetActiveDismissalCount();
+        SigninMetricsUtils.logWebSignin();
+    }
+
     @VisibleForTesting
     public View getBottomSheetViewForTesting() {
         return mView.getContentView();
diff --git a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetMediator.java b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetMediator.java
index b83ca72f..0edc1c3 100644
--- a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetMediator.java
+++ b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetMediator.java
@@ -51,13 +51,8 @@
         mAccountPickerDelegate = accountPickerDelegate;
         mProfileDataCache = ProfileDataCache.createWithDefaultImageSizeAndNoBadge(context);
 
-        OnClickListener onDismissClicked = v -> {
-            SigninMetricsUtils.logAccountConsistencyPromoAction(
-                    AccountConsistencyPromoAction.DISMISSED_BUTTON);
-            SigninPreferencesManager.getInstance()
-                    .incrementAccountPickerBottomSheetActiveDismissalCount();
-            dismissBottomSheetRunnable.run();
-        };
+        OnClickListener onDismissClicked = v -> dismissBottomSheetRunnable.run();
+
         mModel = AccountPickerBottomSheetProperties.createModel(
                 this::onSelectedAccountClicked, this::onContinueAsClicked, onDismissClicked);
         mProfileDataCache.addObserver(mProfileDataSourceObserver);
diff --git a/chrome/browser/subresource_filter/ad_tagging_browsertest.cc b/chrome/browser/subresource_filter/ad_tagging_browsertest.cc
index 95d82bb..40d8ea2 100644
--- a/chrome/browser/subresource_filter/ad_tagging_browsertest.cc
+++ b/chrome/browser/subresource_filter/ad_tagging_browsertest.cc
@@ -10,8 +10,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h"
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.h"
 #include "chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -21,6 +19,8 @@
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/embedder_support/switches.h"
 #include "components/metrics/content/subprocess_metrics_provider.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/frame_data.h"
 #include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
 #include "components/subresource_filter/content/browser/subresource_filter_observer_test_utils.h"
 #include "components/subresource_filter/core/common/test_ruleset_utils.h"
diff --git a/chrome/browser/sync/profile_sync_service_factory_unittest.cc b/chrome/browser/sync/profile_sync_service_factory_unittest.cc
index 16fd6a752..e9d4a04 100644
--- a/chrome/browser/sync/profile_sync_service_factory_unittest.cc
+++ b/chrome/browser/sync/profile_sync_service_factory_unittest.cc
@@ -131,6 +131,7 @@
     datatypes.push_back(syncer::AUTOFILL_PROFILE);
     datatypes.push_back(syncer::AUTOFILL_WALLET_DATA);
     datatypes.push_back(syncer::AUTOFILL_WALLET_METADATA);
+    datatypes.push_back(syncer::AUTOFILL_WALLET_OFFER);
     datatypes.push_back(syncer::BOOKMARKS);
     datatypes.push_back(syncer::DEVICE_INFO);
     datatypes.push_back(syncer::HISTORY_DELETE_DIRECTIVES);
diff --git a/chrome/browser/sync/sync_error_notifier_ash.cc b/chrome/browser/sync/sync_error_notifier_ash.cc
index 409a3d0..48faf7aa 100644
--- a/chrome/browser/sync/sync_error_notifier_ash.cc
+++ b/chrome/browser/sync/sync_error_notifier_ash.cc
@@ -148,8 +148,7 @@
       ash::CreateSystemNotification(
           message_center::NOTIFICATION_TYPE_SIMPLE, notification_id_,
           l10n_util::GetStringUTF16(IDS_SYNC_ERROR_BUBBLE_VIEW_TITLE),
-          l10n_util::GetStringUTF16(parameters.message_id),
-          l10n_util::GetStringUTF16(IDS_SIGNIN_ERROR_DISPLAY_SOURCE),
+          l10n_util::GetStringUTF16(parameters.message_id), base::string16(),
           GURL(notification_id_), notifier_id,
           message_center::RichNotificationData(),
           base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
diff --git a/chrome/browser/sync/test/integration/single_client_nigori_sync_test.cc b/chrome/browser/sync/test/integration/single_client_nigori_sync_test.cc
index dcc23d3b..a560a5b2 100644
--- a/chrome/browser/sync/test/integration/single_client_nigori_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_nigori_sync_test.cc
@@ -26,6 +26,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/sync/base/sync_base_switches.h"
 #include "components/sync/base/time.h"
 #include "components/sync/driver/sync_driver_switches.h"
 #include "components/sync/engine/nigori/nigori.h"
@@ -33,7 +34,9 @@
 #include "components/sync/nigori/cryptographer_impl.h"
 #include "components/sync/nigori/nigori_test_utils.h"
 #include "components/sync/test/fake_server/fake_server_nigori_helper.h"
+#include "components/sync/trusted_vault/fake_security_domains_server.h"
 #include "components/sync/trusted_vault/standalone_trusted_vault_client.h"
+#include "components/sync/trusted_vault/trusted_vault_server_constants.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_launcher.h"
 #include "crypto/ec_private_key.h"
@@ -196,6 +199,43 @@
   }
 };
 
+class FakeSecurityDomainsServerMemberStatusChecker
+    : public StatusChangeChecker {
+ public:
+  FakeSecurityDomainsServerMemberStatusChecker(
+      int expected_member_count,
+      const std::vector<uint8_t>& expected_trusted_vault_key,
+      const syncer::FakeSecurityDomainsServer* server)
+      : expected_member_count_(expected_member_count),
+        expected_trusted_vault_key_(expected_trusted_vault_key),
+        server_(server) {}
+  ~FakeSecurityDomainsServerMemberStatusChecker() override = default;
+
+ protected:
+  // StatusChangeChecker implementation.
+  bool IsExitConditionSatisfied(std::ostream* os) override {
+    *os << "Waiting for security domains server to have members with"
+           " expected key.";
+    if (server_->GetMemberCount() != expected_member_count_) {
+      *os << "Security domains server member count ("
+          << server_->GetMemberCount() << ") doesn't match expected value ("
+          << expected_member_count_ << ").";
+      return false;
+    }
+    if (!server_->AllMembersHaveKey(expected_trusted_vault_key_)) {
+      *os << "Some members in security domains service don't have expected "
+             "key.";
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  int expected_member_count_;
+  std::vector<uint8_t> expected_trusted_vault_key_;
+  const syncer::FakeSecurityDomainsServer* server_;
+};
+
 class SingleClientNigoriSyncTest : public SyncTest {
  public:
   SingleClientNigoriSyncTest() : SyncTest(SINGLE_CLIENT) {}
@@ -1023,4 +1063,72 @@
 #endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
+class SingleClientNigoriSyncTestWithSecurityDomainsServer : public SyncTest {
+ public:
+  SingleClientNigoriSyncTestWithSecurityDomainsServer()
+      : SyncTest(SINGLE_CLIENT) {
+    override_features_.InitAndEnableFeature(
+        switches::kFollowTrustedVaultKeyRotation);
+  }
+  SingleClientNigoriSyncTestWithSecurityDomainsServer(
+      const SingleClientNigoriSyncTestWithSecurityDomainsServer& other) =
+      delete;
+  SingleClientNigoriSyncTestWithSecurityDomainsServer& operator=(
+      const SingleClientNigoriSyncTestWithSecurityDomainsServer& other) =
+      delete;
+  ~SingleClientNigoriSyncTestWithSecurityDomainsServer() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
+    security_domains_server_ =
+        std::make_unique<syncer::FakeSecurityDomainsServer>(
+            embedded_test_server()->base_url());
+    command_line->AppendSwitchASCII(
+        switches::kTrustedVaultServiceURL,
+        security_domains_server_->server_url().spec());
+    SyncTest::SetUpCommandLine(command_line);
+  }
+
+  void SetUpOnMainThread() override {
+    embedded_test_server()->RegisterRequestHandler(
+        base::BindRepeating(&syncer::FakeSecurityDomainsServer::HandleRequest,
+                            base::Unretained(security_domains_server_.get())));
+    embedded_test_server()->StartAcceptingConnections();
+    SyncTest::SetUpOnMainThread();
+  }
+
+  void TearDown() override {
+    // Test server shutdown is required before |security_domains_server_| can be
+    // destroyed.
+    ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
+    SyncTest::TearDown();
+  }
+
+  syncer::FakeSecurityDomainsServer* GetSecurityDomainsServer() {
+    return security_domains_server_.get();
+  }
+
+ private:
+  std::unique_ptr<syncer::FakeSecurityDomainsServer> security_domains_server_;
+  base::test::ScopedFeatureList override_features_;
+};
+
+// Device registration attempt should be taken upon sign in into primary
+// profile. It should be successful when security domain server allows device
+// registration with constant key.
+IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTestWithSecurityDomainsServer,
+                       ShouldRegisterDeviceWithConstantKey) {
+  ASSERT_TRUE(SetupSync());
+  // TODO(crbug.com/1113599): consider checking member public key (requires
+  // either ability to overload key generator in the test or exposing public key
+  // from the client).
+  EXPECT_TRUE(
+      FakeSecurityDomainsServerMemberStatusChecker(
+          /*expected_member_count=*/1,
+          /*expected_trusted_vault_key=*/syncer::GetConstantTrustedVaultKey(),
+          GetSecurityDomainsServer())
+          .Wait());
+  EXPECT_FALSE(GetSecurityDomainsServer()->received_invalid_request());
+}
+
 }  // namespace
diff --git a/chrome/browser/sync/test/integration/sync_integration_test_util.cc b/chrome/browser/sync/test/integration/sync_integration_test_util.cc
index 3fde8fc..6a6433a 100644
--- a/chrome/browser/sync/test/integration/sync_integration_test_util.cc
+++ b/chrome/browser/sync/test/integration/sync_integration_test_util.cc
@@ -6,17 +6,16 @@
 
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/sync/test/integration/themes_helper.h"
+#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "components/sync/driver/profile_sync_service.h"
 #include "content/public/test/test_utils.h"
 
 void SetCustomTheme(Profile* profile, int theme_index) {
+  test::ThemeServiceChangedWaiter waiter(
+      ThemeServiceFactory::GetForProfile(profile));
   themes_helper::UseCustomTheme(profile, theme_index);
-  content::WindowedNotificationObserver theme_change_observer(
-      chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-      content::Source<ThemeService>(
-          ThemeServiceFactory::GetForProfile(profile)));
-  theme_change_observer.Wait();
+  waiter.WaitForThemeChanged();
 }
 
 ServerCountMatchStatusChecker::ServerCountMatchStatusChecker(
diff --git a/chrome/browser/sync/test/integration/themes_helper.cc b/chrome/browser/sync/test/integration/themes_helper.cc
index e4406f2..91a334bb 100644
--- a/chrome/browser/sync/test/integration/themes_helper.cc
+++ b/chrome/browser/sync/test/integration/themes_helper.cc
@@ -116,11 +116,11 @@
     : profile_(profile),
       debug_message_(debug_message),
       exit_condition_(exit_condition) {
-  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-                 content::Source<ThemeService>(GetThemeService(profile_)));
+  GetThemeService(profile_)->AddObserver(this);
 }
 
 ThemeConditionChecker::~ThemeConditionChecker() {
+  GetThemeService(profile_)->RemoveObserver(this);
 }
 
 bool ThemeConditionChecker::IsExitConditionSatisfied(std::ostream* os) {
@@ -128,11 +128,7 @@
   return exit_condition_.Run(GetThemeService(profile_));
 }
 
-void ThemeConditionChecker::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  DCHECK_EQ(chrome::NOTIFICATION_BROWSER_THEME_CHANGED, type);
+void ThemeConditionChecker::OnThemeChanged() {
   CheckExitCondition();
 }
 
diff --git a/chrome/browser/sync/test/integration/themes_helper.h b/chrome/browser/sync/test/integration/themes_helper.h
index 29f7e59..3cda7ce 100644
--- a/chrome/browser/sync/test/integration/themes_helper.h
+++ b/chrome/browser/sync/test/integration/themes_helper.h
@@ -11,6 +11,7 @@
 #include "base/compiler_specific.h"
 #include "chrome/browser/sync/test/integration/status_change_checker.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 
@@ -50,18 +51,12 @@
 
 }  // namespace themes_helper
 
-// Waits until |profile| is using the system theme.
-// Returns false in case of timeout.
-
-// Waits until |profile| is using the default theme.
-// Returns false in case of timeout.
-
 // Helper to wait until a given condition is met, checking every time the
 // current theme changes.
 //
 // The |exit_condition_| closure may be invoked zero or more times.
 class ThemeConditionChecker : public StatusChangeChecker,
-                              public content::NotificationObserver {
+                              public ThemeServiceObserver {
  public:
   ThemeConditionChecker(
       Profile* profile,
@@ -72,17 +67,13 @@
   // Implementation of StatusChangeChecker.
   bool IsExitConditionSatisfied(std::ostream* os) override;
 
-  // Implementation of content::NotificationObserver.
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  // Implementation of ThemeServiceObserver.
+  void OnThemeChanged() override;
 
  private:
   Profile* profile_;
   const std::string debug_message_;
   base::RepeatingCallback<bool(ThemeService*)> exit_condition_;
-
-  content::NotificationRegistrar registrar_;
 };
 
 // Waits until |theme| is pending for install on |profile|.
@@ -115,11 +106,15 @@
   content::NotificationRegistrar registrar_;
 };
 
+// Waits until |profile| is using the system theme.
+// Returns false in case of timeout.
 class SystemThemeChecker : public ThemeConditionChecker {
  public:
   explicit SystemThemeChecker(Profile* profile);
 };
 
+// Waits until |profile| is using the default theme.
+// Returns false in case of timeout.
 class DefaultThemeChecker : public ThemeConditionChecker {
  public:
   explicit DefaultThemeChecker(Profile* profile);
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabData.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabData.java
index 96513e9..000782f 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabData.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabData.java
@@ -88,6 +88,7 @@
     private long mPreviousPriceMicros = NO_PRICE_KNOWN;
 
     private String mCurrencyCode;
+    private String mOfferId;
 
     @VisibleForTesting
     protected ObservableSupplierImpl<Boolean> mIsTabSaveEnabledSupplier =
@@ -364,6 +365,14 @@
         mPreviousPriceMicros = previousPriceMicros;
     }
 
+    public void setOfferID(String offerID) {
+        mOfferId = offerID;
+    }
+
+    public String getOfferId() {
+        return mOfferId;
+    }
+
     /**
      * @return {@link PriceDrop} relating to the offer for the {@link ShoppingPersistedTabData}
      * TODO(crbug.com/1145770) Implement getPriceDrop to only return a result if there is
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorSupplier.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorSupplier.java
index 7dddfcb7..e3846add 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorSupplier.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorSupplier.java
@@ -4,8 +4,11 @@
 
 package org.chromium.chrome.browser.tabmodel;
 
+import androidx.annotation.VisibleForTesting;
+
 import org.chromium.base.UnownedUserDataKey;
 import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.UnownedUserDataSupplier;
 import org.chromium.ui.base.WindowAndroid;
 
@@ -16,9 +19,11 @@
 public class TabModelSelectorSupplier extends UnownedUserDataSupplier<TabModelSelector> {
     private static final UnownedUserDataKey<TabModelSelectorSupplier> KEY =
             new UnownedUserDataKey<TabModelSelectorSupplier>(TabModelSelectorSupplier.class);
+    private static ObservableSupplierImpl<TabModelSelector> sInstanceForTesting;
 
     /** Return {@link TabModelSelector} supplier associated with the given {@link WindowAndroid}. */
     public static ObservableSupplier<TabModelSelector> from(WindowAndroid windowAndroid) {
+        if (sInstanceForTesting != null) return sInstanceForTesting;
         return KEY.retrieveDataFromHost(windowAndroid.getUnownedUserDataHost());
     }
 
@@ -26,4 +31,11 @@
     public TabModelSelectorSupplier() {
         super(KEY);
     }
+
+    /** Sets an instance for testing. */
+    @VisibleForTesting
+    public static void setInstanceForTesting(TabModelSelector tabModelSelector) {
+        sInstanceForTesting = new ObservableSupplierImpl<>();
+        sInstanceForTesting.set(tabModelSelector);
+    }
 }
\ No newline at end of file
diff --git a/chrome/browser/task_manager/sampling/task_manager_impl.cc b/chrome/browser/task_manager/sampling/task_manager_impl.cc
index 5fdd4a1..bea1e98 100644
--- a/chrome/browser/task_manager/sampling/task_manager_impl.cc
+++ b/chrome/browser/task_manager/sampling/task_manager_impl.cc
@@ -53,27 +53,8 @@
 base::LazyInstance<TaskManagerImpl>::DestructorAtExit
     lazy_task_manager_instance = LAZY_INSTANCE_INITIALIZER;
 
-int64_t CalculateNewBytesTransferred(int64_t this_refresh_bytes,
-                                     int64_t last_refresh_bytes) {
-  // Network Service could have restarted between the refresh, causing the
-  // accumulator to be cleared.
-  if (this_refresh_bytes < last_refresh_bytes)
-    return this_refresh_bytes;
-
-  return this_refresh_bytes - last_refresh_bytes;
-}
-
 }  // namespace
 
-size_t BytesTransferredKey::Hasher::operator()(
-    const BytesTransferredKey& key) const {
-  return base::HashInts(key.child_id, key.route_id);
-}
-
-bool BytesTransferredKey::operator==(const BytesTransferredKey& other) const {
-  return child_id == other.child_id && route_id == other.route_id;
-}
-
 TaskManagerImpl::TaskManagerImpl()
     : on_background_data_ready_callback_(base::BindRepeating(
           &TaskManagerImpl::OnTaskGroupBackgroundCalculationsDone,
@@ -466,6 +447,24 @@
   return task->task_id();
 }
 
+void TaskManagerImpl::UpdateAccumulatedStatsNetworkForRoute(
+    int process_id,
+    int route_id,
+    int64_t recv_bytes,
+    int64_t sent_bytes) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  Task* task = GetTaskByRoute(process_id, route_id);
+  if (!task) {
+    // Orphaned/unaccounted activity is credited to the Browser process.
+    task = GetTaskByRoute(content::ChildProcessHost::kInvalidUniqueID,
+                          MSG_ROUTING_NONE);
+  }
+  if (!task)
+    return;
+  task->OnNetworkBytesRead(recv_bytes);
+  task->OnNetworkBytesSent(sent_bytes);
+}
+
 void TaskManagerImpl::TaskAdded(Task* task) {
   DCHECK(task);
 
@@ -530,43 +529,6 @@
   NotifyObserversOnTaskUnresponsive(task->task_id());
 }
 
-void TaskManagerImpl::OnTotalNetworkUsages(
-    std::vector<network::mojom::NetworkUsagePtr> total_network_usages) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  BytesTransferredMap new_total_network_usages_map;
-  for (const auto& entry : total_network_usages) {
-    BytesTransferredKey process_info = {entry->process_id, entry->routing_id};
-    BytesTransferredParam total_bytes_transferred = {
-        entry->total_bytes_received, entry->total_bytes_sent};
-    new_total_network_usages_map[process_info] = total_bytes_transferred;
-
-    auto last_refresh_usage =
-        last_refresh_total_network_usages_map_[process_info];
-    BytesTransferredParam new_bytes_transferred;
-    new_bytes_transferred.byte_read_count =
-        CalculateNewBytesTransferred(total_bytes_transferred.byte_read_count,
-                                     last_refresh_usage.byte_read_count);
-    new_bytes_transferred.byte_sent_count =
-        CalculateNewBytesTransferred(total_bytes_transferred.byte_sent_count,
-                                     last_refresh_usage.byte_sent_count);
-    DCHECK_GE(new_bytes_transferred.byte_read_count, 0);
-    DCHECK_GE(new_bytes_transferred.byte_sent_count, 0);
-
-    if (!UpdateTasksWithBytesTransferred(process_info, new_bytes_transferred)) {
-      // We can't match a task to the notification.  That might mean the
-      // tab that started a download was closed, or the request may have had
-      // no originating task associated with it in the first place.
-      //
-      // Orphaned/unaccounted activity is credited to the Browser process.
-      BytesTransferredKey browser_process_key = {
-          content::ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE};
-      UpdateTasksWithBytesTransferred(browser_process_key,
-                                      new_bytes_transferred);
-    }
-  }
-  last_refresh_total_network_usages_map_.swap(new_total_network_usages_map);
-}
-
 void TaskManagerImpl::OnVideoMemoryUsageStatsUpdate(
     const gpu::VideoMemoryUsageStats& gpu_memory_stats) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -610,14 +572,6 @@
         ->RequestPrivateMemoryFootprint(base::kNullProcessId,
                                         std::move(callback));
   }
-
-  if (TaskManagerObserver::IsResourceRefreshEnabled(
-          REFRESH_TYPE_NETWORK_USAGE, enabled_resources_flags())) {
-    content::GetNetworkService()->GetTotalNetworkUsages(
-        base::BindOnce(&TaskManagerImpl::OnTotalNetworkUsages,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-
   for (auto& groups_itr : task_groups_by_proc_id_) {
     groups_itr.second->Refresh(gpu_memory_stats_,
                                GetCurrentRefreshTime(),
@@ -677,22 +631,6 @@
   return nullptr;
 }
 
-bool TaskManagerImpl::UpdateTasksWithBytesTransferred(
-    const BytesTransferredKey& key,
-    const BytesTransferredParam& param) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  Task* task = GetTaskByRoute(key.child_id, key.route_id);
-  if (task) {
-    task->OnNetworkBytesRead(param.byte_read_count);
-    task->OnNetworkBytesSent(param.byte_sent_count);
-    return true;
-  }
-
-  // Couldn't match the bytes to any existing task.
-  return false;
-}
-
 TaskGroup* TaskManagerImpl::GetTaskGroupByTaskId(TaskId task_id) const {
   auto it = task_groups_by_task_id_.find(task_id);
   DCHECK(it != task_groups_by_task_id_.end());
diff --git a/chrome/browser/task_manager/sampling/task_manager_impl.h b/chrome/browser/task_manager/sampling/task_manager_impl.h
index 12c1fb28..5aafa5b 100644
--- a/chrome/browser/task_manager/sampling/task_manager_impl.h
+++ b/chrome/browser/task_manager/sampling/task_manager_impl.h
@@ -35,38 +35,6 @@
 
 class SharedSampler;
 
-// Identifies the initiator of a network request, by a (child_id,
-// route_id) tuple.
-// BytesTransferredKey supports hashing and may be used as an unordered_map key.
-struct BytesTransferredKey {
-  // The unique ID of the host of the child process requester.
-  int child_id;
-
-  // The ID of the IPC route for the URLRequest (this identifies the
-  // RenderView or like-thing in the renderer that the request gets routed
-  // to).
-  int route_id;
-
-  struct Hasher {
-    size_t operator()(const BytesTransferredKey& key) const;
-  };
-
-  bool operator==(const BytesTransferredKey& other) const;
-};
-
-// This is the entry of the unordered map that tracks bytes transfered by task.
-struct BytesTransferredParam {
-  // The number of bytes read.
-  int64_t byte_read_count = 0;
-
-  // The number of bytes sent.
-  int64_t byte_sent_count = 0;
-};
-
-using BytesTransferredMap = std::unordered_map<BytesTransferredKey,
-                                               BytesTransferredParam,
-                                               BytesTransferredKey::Hasher>;
-
 // Defines a concrete implementation of the TaskManagerInterface.
 class TaskManagerImpl : public TaskManagerInterface,
                         public TaskProviderObserver {
@@ -125,17 +93,16 @@
   bool IsRunningInVM(TaskId task_id) const override;
   TaskId GetTaskIdForWebContents(
       content::WebContents* web_contents) const override;
+  void UpdateAccumulatedStatsNetworkForRoute(int process_id,
+                                             int route_id,
+                                             int64_t recv_bytes,
+                                             int64_t sent_bytes) override;
 
   // task_manager::TaskProviderObserver:
   void TaskAdded(Task* task) override;
   void TaskRemoved(Task* task) override;
   void TaskUnresponsive(Task* task) override;
 
-  // Used when Network Service is enabled.
-  // Receives total network usages from |NetworkService|.
-  void OnTotalNetworkUsages(
-      std::vector<network::mojom::NetworkUsagePtr> total_network_usages);
-
  private:
   using PidToTaskGroupMap =
       std::map<base::ProcessId, std::unique_ptr<TaskGroup>>;
@@ -158,13 +125,6 @@
   // Lookup a task by child_id and possibly route_id.
   Task* GetTaskByRoute(int child_id, int route_id) const;
 
-  // Based on |param| the appropriate task will be updated by its network usage.
-  // Returns true if it was able to match |param| to an existing task, returns
-  // false otherwise, at which point the caller must explicitly match these
-  // bytes to the browser process by calling this method again with
-  // |param.origin_pid = 0| and |param.child_id = param.route_id = -1|.
-  bool UpdateTasksWithBytesTransferred(const BytesTransferredKey& key,
-                                       const BytesTransferredParam& param);
 
   PidToTaskGroupMap* GetVmPidToTaskGroupMap(Task::Type type);
   TaskGroup* GetTaskGroupByTaskId(TaskId task_id) const;
@@ -192,11 +152,6 @@
   // A cached sorted list of the task IDs.
   mutable std::vector<TaskId> sorted_task_ids_;
 
-  // Used when Network Service is enabled.
-  // Stores the total network usages per |process_id, routing_id| from last
-  // refresh.
-  BytesTransferredMap last_refresh_total_network_usages_map_;
-
   // The list of the task providers that are owned and observed by this task
   // manager implementation.
   std::vector<std::unique_ptr<TaskProvider>> task_providers_;
diff --git a/chrome/browser/task_manager/task_manager_interface.h b/chrome/browser/task_manager/task_manager_interface.h
index 8915b3a..45e58cb 100644
--- a/chrome/browser/task_manager/task_manager_interface.h
+++ b/chrome/browser/task_manager/task_manager_interface.h
@@ -224,6 +224,14 @@
   virtual TaskId GetTaskIdForWebContents(
       content::WebContents* web_contents) const = 0;
 
+  // Update the accumulated network stats with additional data sent/received
+  // for a route described by |process_id| and |route_id|. If the associated
+  // task cannot be found it will be attributed to the browser process task.
+  virtual void UpdateAccumulatedStatsNetworkForRoute(int process_id,
+                                                     int route_id,
+                                                     int64_t recv_bytes,
+                                                     int64_t sent_bytes) = 0;
+
   // Returns true if the resource |type| usage calculation is enabled and
   // the implementation should refresh its value (this means that at least one
   // of the observers require this value). False otherwise.
diff --git a/chrome/browser/task_manager/test_task_manager.cc b/chrome/browser/task_manager/test_task_manager.cc
index 604c6996..19fa1f03 100644
--- a/chrome/browser/task_manager/test_task_manager.cc
+++ b/chrome/browser/task_manager/test_task_manager.cc
@@ -182,6 +182,12 @@
   return -1;
 }
 
+void TestTaskManager::UpdateAccumulatedStatsNetworkForRoute(
+    int process_id,
+    int route_id,
+    int64_t recv_bytes,
+    int64_t sent_bytes) {}
+
 base::TimeDelta TestTaskManager::GetRefreshTime() {
   return GetCurrentRefreshTime();
 }
diff --git a/chrome/browser/task_manager/test_task_manager.h b/chrome/browser/task_manager/test_task_manager.h
index c27d3a8a..31421fd 100644
--- a/chrome/browser/task_manager/test_task_manager.h
+++ b/chrome/browser/task_manager/test_task_manager.h
@@ -72,6 +72,10 @@
   bool IsRunningInVM(TaskId task_id) const override;
   TaskId GetTaskIdForWebContents(
       content::WebContents* web_contents) const override;
+  void UpdateAccumulatedStatsNetworkForRoute(int process_id,
+                                             int route_id,
+                                             int64_t recv_bytes,
+                                             int64_t sent_bytes) override;
 
   base::TimeDelta GetRefreshTime();
   int64_t GetEnabledFlags();
diff --git a/chrome/browser/themes/test/theme_service_changed_waiter.cc b/chrome/browser/themes/test/theme_service_changed_waiter.cc
new file mode 100644
index 0000000..fde7aff
--- /dev/null
+++ b/chrome/browser/themes/test/theme_service_changed_waiter.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 "chrome/browser/themes/test/theme_service_changed_waiter.h"
+
+#include "chrome/browser/themes/theme_service.h"
+
+namespace test {
+
+ThemeServiceChangedWaiter::ThemeServiceChangedWaiter(ThemeService* service)
+    : service_(service) {
+  DCHECK(service_);
+  service_->AddObserver(this);
+}
+
+ThemeServiceChangedWaiter::~ThemeServiceChangedWaiter() {
+  service_->RemoveObserver(this);
+}
+
+void ThemeServiceChangedWaiter::OnThemeChanged() {
+  service_->RemoveObserver(this);
+  run_loop_.Quit();
+}
+
+void ThemeServiceChangedWaiter::WaitForThemeChanged() {
+  run_loop_.Run();
+}
+
+}  // namespace test
diff --git a/chrome/browser/themes/test/theme_service_changed_waiter.h b/chrome/browser/themes/test/theme_service_changed_waiter.h
new file mode 100644
index 0000000..5161f63
--- /dev/null
+++ b/chrome/browser/themes/test/theme_service_changed_waiter.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 CHROME_BROWSER_THEMES_TEST_THEME_SERVICE_CHANGED_WAITER_H_
+#define CHROME_BROWSER_THEMES_TEST_THEME_SERVICE_CHANGED_WAITER_H_
+
+#include "base/run_loop.h"
+#include "chrome/browser/themes/theme_service_observer.h"
+
+class ThemeService;
+
+namespace test {
+
+// Waits for a call to |OnThemeChanged()| for testing. It starts observing calls
+// on construction, and can only be used to wait once.
+class ThemeServiceChangedWaiter : public ThemeServiceObserver {
+ public:
+  explicit ThemeServiceChangedWaiter(ThemeService* service);
+  ThemeServiceChangedWaiter(const ThemeServiceChangedWaiter&) = delete;
+  ThemeServiceChangedWaiter& operator=(const ThemeServiceChangedWaiter&) =
+      delete;
+  ~ThemeServiceChangedWaiter() override;
+
+  // ThemeServiceObserver implementation.
+  void OnThemeChanged() override;
+
+  // Waits for an |OnThemeChanged()| call from |service_|.
+  void WaitForThemeChanged();
+
+ private:
+  base::RunLoop run_loop_;
+
+  ThemeService* const service_;
+};
+
+}  // namespace test
+
+#endif  // CHROME_BROWSER_THEMES_TEST_THEME_SERVICE_CHANGED_WAITER_H_
diff --git a/chrome/browser/themes/theme_service.cc b/chrome/browser/themes/theme_service.cc
index 08cafea4..be75769 100644
--- a/chrome/browser/themes/theme_service.cc
+++ b/chrome/browser/themes/theme_service.cc
@@ -26,7 +26,6 @@
 #include "base/task/thread_pool.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/theme_installed_infobar_delegate.h"
 #include "chrome/browser/infobars/infobar_service.h"
@@ -37,6 +36,7 @@
 #include "chrome/browser/themes/increased_contrast_theme_supplier.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/themes/theme_service_factory.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "chrome/browser/themes/theme_syncable_service.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/common/buildflags.h"
@@ -501,6 +501,14 @@
                                             std::move(reinstall_callback));
 }
 
+void ThemeService::AddObserver(ThemeServiceObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void ThemeService::RemoveObserver(ThemeServiceObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
 void ThemeService::SetCustomDefaultTheme(
     scoped_refptr<CustomThemeSupplier> theme_supplier) {
   ClearAllThemeData();
@@ -574,17 +582,9 @@
   if (!ready_)
     return;
 
-  DVLOG(1) << "Sending BROWSER_THEME_CHANGED";
-  // Redraw!
-  content::NotificationService* service =
-      content::NotificationService::current();
-  service->Notify(chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-                  content::Source<ThemeService>(this),
-                  content::NotificationService::NoDetails());
-  // Notify sync that theme has changed.
-  if (theme_syncable_service_.get()) {
-    theme_syncable_service_->OnThemeChange();
-  }
+  // Redraw and notify sync that theme has changed.
+  for (auto& observer : observers_)
+    observer.OnThemeChanged();
 }
 
 void ThemeService::FixInconsistentPreferencesIfNeeded() {}
@@ -768,7 +768,7 @@
 
   if (registry->GetInstalledExtension(extension_id)) {
     // Do not disable the previous theme if it is already uninstalled. Sending
-    // NOTIFICATION_BROWSER_THEME_CHANGED causes the previous theme to be
+    // |ThemeServiceObserver::OnThemeChanged()| causes the previous theme to be
     // uninstalled when the notification causes the remaining infobar to close
     // and does not open any new infobars. See crbug.com/468280.
     service->DisableExtension(extension_id,
diff --git a/chrome/browser/themes/theme_service.h b/chrome/browser/themes/theme_service.h
index 6f41c4c..bf34208 100644
--- a/chrome/browser/themes/theme_service.h
+++ b/chrome/browser/themes/theme_service.h
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
 #include "base/scoped_observer.h"
 #include "base/task/cancelable_task_tracker.h"
 #include "chrome/browser/themes/theme_helper.h"
@@ -26,6 +27,7 @@
 
 class BrowserThemePack;
 class CustomThemeSupplier;
+class ThemeServiceObserver;
 class ThemeSyncableService;
 class Profile;
 
@@ -146,6 +148,10 @@
   std::unique_ptr<ThemeService::ThemeReinstaller>
   BuildReinstallerForCurrentTheme();
 
+  void AddObserver(ThemeServiceObserver* observer);
+
+  void RemoveObserver(ThemeServiceObserver* observer);
+
   const ThemeHelper& theme_helper_for_testing() const { return theme_helper_; }
 
   // Don't create "Cached Theme.pak" in the extension directory, for testing.
@@ -294,6 +300,8 @@
   ScopedObserver<ui::NativeTheme, ui::NativeThemeObserver>
       native_theme_observer_{this};
 
+  base::ObserverList<ThemeServiceObserver> observers_;
+
   base::WeakPtrFactory<ThemeService> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ThemeService);
diff --git a/chrome/browser/themes/theme_service_browsertest.cc b/chrome/browser/themes/theme_service_browsertest.cc
index f150e74..c1b9a7e 100644
--- a/chrome/browser/themes/theme_service_browsertest.cc
+++ b/chrome/browser/themes/theme_service_browsertest.cc
@@ -5,10 +5,10 @@
 #include "chrome/browser/themes/theme_service.h"
 
 #include "base/macros.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/component_loader.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/browser.h"
@@ -59,11 +59,9 @@
   EXPECT_EQ(base::FilePath(),
             profile->GetPrefs()->GetFilePath(prefs::kCurrentThemePackFilename));
 
-  content::WindowedNotificationObserver theme_change_observer(
-      chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-      content::Source<ThemeService>(theme_service));
+  test::ThemeServiceChangedWaiter waiter(theme_service);
   InstallExtension(test_data_dir_.AppendASCII("theme"), 1);
-  theme_change_observer.Wait();
+  waiter.WaitForThemeChanged();
 
   // Check that the theme was installed.
   EXPECT_TRUE(UsingCustomTheme(*theme_service));
diff --git a/chrome/browser/themes/theme_service_observer.h b/chrome/browser/themes/theme_service_observer.h
new file mode 100644
index 0000000..61f8897c
--- /dev/null
+++ b/chrome/browser/themes/theme_service_observer.h
@@ -0,0 +1,19 @@
+// 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_THEMES_THEME_SERVICE_OBSERVER_H_
+#define CHROME_BROWSER_THEMES_THEME_SERVICE_OBSERVER_H_
+
+#include "base/observer_list_types.h"
+
+class ThemeServiceObserver : public base::CheckedObserver {
+ public:
+  // Called when the user has changed the browser theme.
+  virtual void OnThemeChanged() = 0;
+
+ protected:
+  ~ThemeServiceObserver() override = default;
+};
+
+#endif  // CHROME_BROWSER_THEMES_THEME_SERVICE_OBSERVER_H_
diff --git a/chrome/browser/themes/theme_service_unittest.cc b/chrome/browser/themes/theme_service_unittest.cc
index 122cdea..f1655e2 100644
--- a/chrome/browser/themes/theme_service_unittest.cc
+++ b/chrome/browser/themes/theme_service_unittest.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/extensions/unpacked_installer.h"
 #include "chrome/browser/themes/custom_theme_supplier.h"
 #include "chrome/browser/themes/increased_contrast_theme_supplier.h"
+#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/common/buildflags.h"
@@ -25,8 +26,6 @@
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/test_extension_registry_observer.h"
@@ -105,6 +104,7 @@
   ThemeScoper LoadUnpackedTheme(const std::string& source_file_path =
                                     "extensions/theme_minimal/manifest.json") {
     ThemeScoper scoper(service_, registry_);
+    test::ThemeServiceChangedWaiter waiter(theme_service_);
     base::FilePath temp_dir = scoper.GetTempPath();
     base::FilePath dst_manifest_path = temp_dir.AppendASCII("manifest.json");
     base::FilePath test_data_dir;
@@ -119,7 +119,7 @@
     installer->Load(temp_dir);
     scoper.set_extension_id(observer.WaitForExtensionLoaded()->id());
 
-    WaitForThemeInstall();
+    waiter.WaitForThemeChanged();
 
     return scoper;
   }
@@ -164,13 +164,6 @@
     return color.value_or(gfx::kPlaceholderColor);
   }
 
-  void WaitForThemeInstall() {
-    content::WindowedNotificationObserver theme_change_observer(
-        chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-        content::Source<ThemeService>(theme_service_));
-    theme_change_observer.Wait();
-  }
-
   bool IsExtensionDisabled(const std::string& id) const {
     return registry_->GetExtensionById(id,
                                        extensions::ExtensionRegistry::DISABLED);
@@ -226,8 +219,11 @@
   EXPECT_TRUE(IsExtensionDisabled(scoper1.extension_id()));
 
   // 2) Enabling a disabled theme extension should swap the current theme.
-  service_->EnableExtension(scoper1.extension_id());
-  WaitForThemeInstall();
+  {
+    test::ThemeServiceChangedWaiter waiter(theme_service_);
+    service_->EnableExtension(scoper1.extension_id());
+    waiter.WaitForThemeChanged();
+  }
   EXPECT_EQ(scoper1.extension_id(), theme_service_->GetThemeID());
   EXPECT_TRUE(service_->IsExtensionEnabled(scoper1.extension_id()));
   EXPECT_TRUE(IsExtensionDisabled(scoper2.extension_id()));
@@ -235,8 +231,11 @@
   // 3) Using RevertToExtensionTheme() with a disabled theme should enable and
   // set the theme. This is the case when the user reverts to the previous theme
   // via an infobar.
-  theme_service_->RevertToExtensionTheme(scoper2.extension_id());
-  WaitForThemeInstall();
+  {
+    test::ThemeServiceChangedWaiter waiter(theme_service_);
+    theme_service_->RevertToExtensionTheme(scoper2.extension_id());
+    waiter.WaitForThemeChanged();
+  }
   EXPECT_EQ(scoper2.extension_id(), theme_service_->GetThemeID());
   EXPECT_TRUE(service_->IsExtensionEnabled(scoper2.extension_id()));
   EXPECT_TRUE(IsExtensionDisabled(scoper1.extension_id()));
@@ -265,14 +264,12 @@
   EXPECT_EQ(scoper2.extension_id(), theme_service_->GetThemeID());
 
   // 1) Upgrading the current theme should not revert to the default theme.
-  content::WindowedNotificationObserver theme_change_observer(
-      chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-      content::Source<ThemeService>(theme_service_));
+  test::ThemeServiceChangedWaiter waiter(theme_service_);
   UpdateUnpackedTheme(scoper2.extension_id());
 
   // The ThemeService should have sent an theme change notification even though
   // the id of the current theme did not change.
-  theme_change_observer.Wait();
+  waiter.WaitForThemeChanged();
 
   EXPECT_EQ(scoper2.extension_id(), theme_service_->GetThemeID());
   EXPECT_TRUE(IsExtensionDisabled(scoper1.extension_id()));
@@ -398,8 +395,9 @@
     EXPECT_TRUE(IsExtensionDisabled(scoper1.extension_id()));
     EXPECT_EQ(scoper2.extension_id(), theme_service_->GetThemeID());
 
+    test::ThemeServiceChangedWaiter waiter(theme_service_);
     reinstaller->Reinstall();
-    WaitForThemeInstall();
+    waiter.WaitForThemeChanged();
     base::RunLoop().RunUntilIdle();
     EXPECT_TRUE(IsExtensionDisabled(scoper2.extension_id()));
     EXPECT_EQ(scoper1.extension_id(), theme_service_->GetThemeID());
diff --git a/chrome/browser/themes/theme_syncable_service.cc b/chrome/browser/themes/theme_syncable_service.cc
index a447fba..71b02f3 100644
--- a/chrome/browser/themes/theme_syncable_service.cc
+++ b/chrome/browser/themes/theme_syncable_service.cc
@@ -45,12 +45,14 @@
       theme_service_(theme_service),
       use_system_theme_by_default_(false) {
   DCHECK(theme_service_);
+  theme_service_->AddObserver(this);
 }
 
 ThemeSyncableService::~ThemeSyncableService() {
+  theme_service_->RemoveObserver(this);
 }
 
-void ThemeSyncableService::OnThemeChange() {
+void ThemeSyncableService::OnThemeChanged() {
   if (sync_processor_.get()) {
     sync_pb::ThemeSpecifics current_specifics;
     if (!GetThemeSpecificsFromCurrentTheme(&current_specifics))
diff --git a/chrome/browser/themes/theme_syncable_service.h b/chrome/browser/themes/theme_syncable_service.h
index a43d4d7..ea97ef4b 100644
--- a/chrome/browser/themes/theme_syncable_service.h
+++ b/chrome/browser/themes/theme_syncable_service.h
@@ -12,6 +12,7 @@
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
 #include "base/threading/thread_checker.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "components/sync/model/sync_change.h"
 #include "components/sync/model/sync_data.h"
 #include "components/sync/model/sync_error.h"
@@ -26,7 +27,8 @@
 class ThemeSpecifics;
 }
 
-class ThemeSyncableService : public syncer::SyncableService {
+class ThemeSyncableService : public syncer::SyncableService,
+                             public ThemeServiceObserver {
  public:
   class Observer : public base::CheckedObserver {
    public:
@@ -42,8 +44,8 @@
 
   static syncer::ModelType model_type() { return syncer::THEMES; }
 
-  // Called by ThemeService when user changes theme.
-  void OnThemeChange();
+  // ThemeServiceObserver implementation.
+  void OnThemeChanged() override;
 
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
diff --git a/chrome/browser/themes/theme_syncable_service_unittest.cc b/chrome/browser/themes/theme_syncable_service_unittest.cc
index d50349d0..953325c7 100644
--- a/chrome/browser/themes/theme_syncable_service_unittest.cc
+++ b/chrome/browser/themes/theme_syncable_service_unittest.cc
@@ -195,6 +195,7 @@
   }
 
   void TearDown() override {
+    theme_sync_service_.reset();
     profile_.reset();
     base::RunLoop().RunUntilIdle();
   }
@@ -627,7 +628,7 @@
 
   // Change current theme to custom theme and notify theme_sync_service_.
   fake_theme_service_->SetTheme(theme_extension_.get());
-  theme_sync_service_->OnThemeChange();
+  theme_sync_service_->OnThemeChanged();
   EXPECT_EQ(1u, changes.size());
   const sync_pb::ThemeSpecifics& change_specifics =
       changes[0].sync_data().GetSpecifics().theme();
@@ -659,7 +660,7 @@
   // Change current theme to custom theme and notify theme_sync_service_.
   fake_theme_service_->BuildAutogeneratedThemeFromColor(
       SkColorSetRGB(0, 0, 100));
-  theme_sync_service_->OnThemeChange();
+  theme_sync_service_->OnThemeChanged();
   EXPECT_EQ(1u, changes.size());
   const sync_pb::ThemeSpecifics& change_specifics =
       changes[0].sync_data().GetSpecifics().theme();
@@ -691,7 +692,7 @@
   // Change current theme to custom theme and notify theme_sync_service_.
   // No change is output because sync has stopped.
   fake_theme_service_->SetTheme(theme_extension_.get());
-  theme_sync_service_->OnThemeChange();
+  theme_sync_service_->OnThemeChanged();
   EXPECT_EQ(0u, changes.size());
 
   // ProcessSyncChanges() should return error when sync has stopped.
@@ -719,7 +720,7 @@
   // Change to custom theme and notify theme_sync_service_.
   // use_system_theme_by_default bit should be preserved.
   fake_theme_service_->SetTheme(theme_extension_.get());
-  theme_sync_service_->OnThemeChange();
+  theme_sync_service_->OnThemeChanged();
   const syncer::SyncChangeList& changes = fake_change_processor_->changes();
   EXPECT_EQ(1u, changes.size());
   const sync_pb::ThemeSpecifics& change_specifics =
@@ -748,7 +749,7 @@
   // Change to default theme and notify theme_sync_service_.
   // use_system_theme_by_default bit should be false.
   fake_theme_service_->UseDefaultTheme();
-  theme_sync_service_->OnThemeChange();
+  theme_sync_service_->OnThemeChanged();
   syncer::SyncChangeList& changes = fake_change_processor_->changes();
   EXPECT_EQ(1u, changes.size());
   EXPECT_FALSE(changes[0]
@@ -761,7 +762,7 @@
   // use_system_theme_by_default bit should be true.
   changes.clear();
   fake_theme_service_->UseSystemTheme();
-  theme_sync_service_->OnThemeChange();
+  theme_sync_service_->OnThemeChanged();
   EXPECT_EQ(1u, changes.size());
   EXPECT_TRUE(changes[0]
                   .sync_data()
@@ -796,7 +797,7 @@
   // Change to default theme and notify theme_sync_service_.
   // use_system_theme_by_default bit should be preserved.
   fake_theme_service_->UseDefaultTheme();
-  theme_sync_service_->OnThemeChange();
+  theme_sync_service_->OnThemeChanged();
   const syncer::SyncChangeList& changes = fake_change_processor_->changes();
   EXPECT_EQ(1u, changes.size());
   const sync_pb::ThemeSpecifics& change_specifics =
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 2245563..b43506b8 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -191,7 +191,8 @@
     "webauthn/authenticator_request_dialog.h",
     "webid/identity_dialog_controller.cc",
     "webid/identity_dialog_controller.h",
-    "webid/identity_dialogs.h",
+    "webid/webid_dialog.cc",
+    "webid/webid_dialog.h",
     "webui/about_ui.cc",
     "webui/about_ui.h",
     "webui/autofill_and_password_manager_internals/autofill_internals_ui.cc",
@@ -781,7 +782,7 @@
       "android/tab_model/tab_model_observer_jni_bridge.h",
       "android/toolbar/location_bar_model_android.cc",
       "android/toolbar/location_bar_model_android.h",
-      "android/webid/identity_dialogs_android.cc",
+      "android/webid/webid_dialog_android.cc",
       "browser_otr_state_android.cc",
       "javascript_dialogs/javascript_tab_modal_dialog_manager_delegate_android.cc",
       "javascript_dialogs/javascript_tab_modal_dialog_manager_delegate_android.h",
@@ -4281,10 +4282,12 @@
       "views/webauthn/webauthn_hover_button.h",
       "views/webauthn/webauthn_icon_view.cc",
       "views/webauthn/webauthn_icon_view.h",
-      "views/webid/webid_permission_dialog.cc",
-      "views/webid/webid_permission_dialog.h",
-      "views/webid/webid_signin_window.cc",
-      "views/webid/webid_signin_window.h",
+      "views/webid/webid_dialog_views.cc",
+      "views/webid/webid_dialog_views.h",
+      "views/webid/webid_permission_view.cc",
+      "views/webid/webid_permission_view.h",
+      "views/webid/webid_signin_page_view.cc",
+      "views/webid/webid_signin_page_view.h",
       "views/window_name_prompt.cc",
       "webauthn/account_hover_list_model.cc",
       "webauthn/account_hover_list_model.h",
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuAdapter.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuAdapter.java
index 6eb57937..dbf4cce 100644
--- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuAdapter.java
+++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuAdapter.java
@@ -26,8 +26,6 @@
 import androidx.appcompat.content.res.AppCompatResources;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.chrome.browser.flags.CachedFeatureFlags;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.ui.appmenu.internal.R;
 import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter;
 import org.chromium.components.browser_ui.widget.text.TextViewWithCompoundDrawables;
@@ -498,13 +496,9 @@
             setupImageButton(holder.buttons[i], item.getSubMenu().getItem(i));
         }
 
-        if (CachedFeatureFlags.isEnabled(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_ICONS)
-                || CachedFeatureFlags.isEnabled(
-                        ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR)) {
-            // Tint action bar's background.
-            convertView.setBackgroundDrawable(ApiCompatibilityUtils.getDrawable(
-                    convertView.getContext().getResources(), R.drawable.menu_action_bar_bg));
-        }
+        // Tint action bar's background.
+        convertView.setBackgroundDrawable(ApiCompatibilityUtils.getDrawable(
+                convertView.getContext().getResources(), R.drawable.menu_action_bar_bg));
 
         convertView.setFocusable(false);
         convertView.setEnabled(false);
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuAdapterRenderTest.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuAdapterRenderTest.java
index f03446c..5e4befa 100644
--- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuAdapterRenderTest.java
+++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuAdapterRenderTest.java
@@ -174,7 +174,7 @@
         MenuItem item = buildIconRow(1, 2, TITLE_1, icon1, 3, TITLE_2, icon2, 4, TITLE_3, icon3, 0,
                 "", null, 0, "", null, enabled);
 
-        mRenderTestRule.render(createView(item, false), "iconrow_three_icons");
+        mRenderTestRule.render(createView(item, false), "tinted_iconrow_three_icons");
     }
 
     @Test
@@ -193,7 +193,7 @@
         MenuItem item = buildIconRow(1, 2, TITLE_1, icon1, 3, TITLE_2, icon2, 4, TITLE_3, icon3, 5,
                 TITLE_4, icon4, 0, "", null, true);
 
-        mRenderTestRule.render(createView(item, false), "iconrow_four_icons");
+        mRenderTestRule.render(createView(item, false), "tinted_iconrow_four_icons");
     }
 
     @Test
@@ -214,7 +214,7 @@
         MenuItem item = buildIconRow(1, 2, TITLE_1, icon1, 3, TITLE_2, icon2, 4, TITLE_3, icon3, 5,
                 TITLE_4, icon4, 6, TITLE_5, icon5, true);
 
-        mRenderTestRule.render(createView(item, false), "iconrow_five_icons");
+        mRenderTestRule.render(createView(item, false), "tinted_iconrow_five_icons");
     }
 
     private void setRenderTestPrefix(boolean enabled) {
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index e6c79cc6..b7cedb3 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -1674,9 +1674,6 @@
         Tabs that you've opened in Chrome on your other devices will appear here.
       </message>
 
-      <message name="IDS_SIGN_IN_SYNC" desc="Sync preference title in signed-in prefs and prefix for notification related to sync [CHAR-LIMIT=32]">
-        Sync
-      </message>
       <message name="IDS_SEARCH_AND_BROWSE_CATEGORY" desc="Category to show the search and browse improvement toggle. [CHAR-LIMT=32]">
         Search and browse
       </message>
@@ -1687,7 +1684,7 @@
         Sync everything
       </message>
       <message name="IDS_SYNC_AUTOFILL" desc="Title for preference which enables sync'ing of autofill. [CHAR-LIMT=32]">
-        Autofill
+        Addresses and more
       </message>
       <message name="IDS_SYNC_BOOKMARKS" desc="Title for preference which enables sync'ing of bookmarks. [CHAR-LIMT=32]">
         Bookmarks
@@ -1705,7 +1702,7 @@
         Settings
       </message>
       <message name="IDS_SYNC_PAYMENTS_INTEGRATION" desc="Title for preference which enables import of Google Pay data for Autofill. 'Google Pay' should not be translated as it is the product name.">
-        Credit cards and addresses using Google Pay
+        Payment methods and addresses using Google Pay
       </message>
       <message name="IDS_SYNC_ENCRYPTION" desc="Preference category name for sync encryption. [CHAR-LIMT=32]">
         Encryption
@@ -1717,7 +1714,7 @@
         Encryption
       </message>
       <message name="IDS_SYNC_PASSPHRASE_TYPE_KEYSTORE" desc="Option to encrypt only passwords, using your Google credentials.">
-        Encrypt passwords with Google credentials
+        Encrypt synced passwords with your Google Account
       </message>
       <message name="IDS_SYNC_PASSPHRASE_TYPE_FROZEN" desc="Option to encrypt sync data, using your Google password frozen on the substituted date.">
         Encrypt synced data with Google password as of <ph name="TIME">%1$s<ex>Oct 3, 2014</ex></ph>
@@ -1837,7 +1834,7 @@
         Sync isn't working
       </message>
       <message name="IDS_PASSWORD_SYNC_ERROR_SUMMARY" desc="Short message shown in various UIs (e.g. Sync Error Card body) to inform that passwords can't be synced (due to missing trusted vault keys). [CHAR-LIMIT=32]">
-        Error syncing passwords
+        Password sync isn't working
       </message>
       <message name="IDS_SYNC_SETTINGS_NOT_CONFIRMED_TITLE" desc="Title of the error message shown when sync setup was not complete. [CHAR-LIMIT=60]">
         Initial sync setup not finished
@@ -1848,11 +1845,14 @@
       <message name="IDS_HINT_SYNC_AUTH_ERROR" desc="Hint message to resolve sync auth error.">
         Sign in again to start sync
       </message>
-      <message name="IDS_HINT_SYNC_RETRIEVE_KEYS" desc="Hint message to resolve sync encryption error.">
-        Fix now
+      <message name="IDS_HINT_SYNC_RETRIEVE_KEYS_FOR_EVERYTHING" desc="Hint message to resolve sync encryption error, required to resume sync.">
+        To start sync, verify it's you
+      </message>
+      <message name="IDS_HINT_SYNC_RETRIEVE_KEYS_FOR_PASSWORDS" desc="Hint message to resolve sync encryption error, required to resume syncing passwords.">
+        To sync your passwords, verify it's you
       </message>
       <message name="IDS_HINT_PASSPHRASE_REQUIRED" desc="Hint message to resolve passphrase required error.">
-        Enter your passphrase to start sync
+        To start sync, enter your passphrase
       </message>
       <message name="IDS_HINT_SYNC_SETTINGS_NOT_CONFIRMED_DESCRIPTION" desc="The error message to display when sign-in was interrupted and the user didn't review the sync settings.">
         Choose what to sync below
@@ -1880,6 +1880,9 @@
       <message name="IDS_AUTH_ERROR_CARD_BUTTON" desc="Button text for auth error in sync error cards.">
         Sign in again
       </message>
+      <message name="IDS_TRUSTED_VAULT_ERROR_CARD_BUTTON" desc="Button text for trusted vault error in sync error cards.">
+        Verify it's you
+      </message>
 
       <!-- Sync error strings -->
       <message name="IDS_SYNC_ERROR_GENERIC" desc="Sync error string for generic error. [CHAR-LIMIT=80]">
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HINT_PASSPHRASE_REQUIRED.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HINT_PASSPHRASE_REQUIRED.png.sha1
new file mode 100644
index 0000000..b0db6ee3
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HINT_PASSPHRASE_REQUIRED.png.sha1
@@ -0,0 +1 @@
+9272bebc93956b5db16964e2d9ccc89c3def8fc6
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HINT_SYNC_RETRIEVE_KEYS.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HINT_SYNC_RETRIEVE_KEYS.png.sha1
deleted file mode 100644
index 4602127..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HINT_SYNC_RETRIEVE_KEYS.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-4f2e743dff46b212f4c688b74a6956206c58d426
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HINT_SYNC_RETRIEVE_KEYS_FOR_EVERYTHING.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HINT_SYNC_RETRIEVE_KEYS_FOR_EVERYTHING.png.sha1
new file mode 100644
index 0000000..e10e5e8f
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HINT_SYNC_RETRIEVE_KEYS_FOR_EVERYTHING.png.sha1
@@ -0,0 +1 @@
+78dab216781f2b369417efe6b7f816e83cd045cf
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HINT_SYNC_RETRIEVE_KEYS_FOR_PASSWORDS.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HINT_SYNC_RETRIEVE_KEYS_FOR_PASSWORDS.png.sha1
new file mode 100644
index 0000000..a98181a4
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HINT_SYNC_RETRIEVE_KEYS_FOR_PASSWORDS.png.sha1
@@ -0,0 +1 @@
+9fa2666cb7ff4870838eeb4c8744f679f063518a
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PASSWORD_SYNC_ERROR_SUMMARY.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PASSWORD_SYNC_ERROR_SUMMARY.png.sha1
index ce367b6e..3388d77b 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PASSWORD_SYNC_ERROR_SUMMARY.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PASSWORD_SYNC_ERROR_SUMMARY.png.sha1
@@ -1 +1 @@
-e764a38ab896e3093fe21fa2e183a358dde4cb3a
\ No newline at end of file
+8b1b72bde9bc0490edeb06206a26a6b7e3d498df
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SYNC_AUTOFILL.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SYNC_AUTOFILL.png.sha1
new file mode 100644
index 0000000..23ae2c6c3
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SYNC_AUTOFILL.png.sha1
@@ -0,0 +1 @@
+6fa426fa64c8f98b4fecd9afd33fac8606df0d8f
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SYNC_PASSPHRASE_TYPE_KEYSTORE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SYNC_PASSPHRASE_TYPE_KEYSTORE.png.sha1
new file mode 100644
index 0000000..5963d99
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SYNC_PASSPHRASE_TYPE_KEYSTORE.png.sha1
@@ -0,0 +1 @@
+922d6aca59972b6279706f68ac32f61c9ebeefda
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SYNC_PAYMENTS_INTEGRATION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SYNC_PAYMENTS_INTEGRATION.png.sha1
new file mode 100644
index 0000000..23ae2c6c3
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SYNC_PAYMENTS_INTEGRATION.png.sha1
@@ -0,0 +1 @@
+6fa426fa64c8f98b4fecd9afd33fac8606df0d8f
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TRUSTED_VAULT_ERROR_CARD_BUTTON.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TRUSTED_VAULT_ERROR_CARD_BUTTON.png.sha1
new file mode 100644
index 0000000..a98181a4
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TRUSTED_VAULT_ERROR_CARD_BUTTON.png.sha1
@@ -0,0 +1 @@
+9fa2666cb7ff4870838eeb4c8744f679f063518a
\ No newline at end of file
diff --git a/chrome/browser/ui/android/webid/identity_dialogs_android.cc b/chrome/browser/ui/android/webid/identity_dialogs_android.cc
deleted file mode 100644
index 6448343..0000000
--- a/chrome/browser/ui/android/webid/identity_dialogs_android.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 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/webid/identity_dialogs.h"
-
-#include <string>
-#include "base/callback.h"
-#include "base/notreached.h"
-#include "base/strings/string16.h"
-#include "content/public/browser/identity_request_dialog_controller.h"
-#include "content/public/browser/web_contents.h"
-#include "url/gurl.h"
-
-// Stub implementations for Identity UI on Android.
-void ShowInitialWebIdPermissionDialog(
-    content::WebContents* rp_web_contents,
-    const base::string16& idp_hostname,
-    const base::string16& rp_hostname,
-    content::IdentityRequestDialogController::InitialApprovalCallback
-        callback) {
-  NOTIMPLEMENTED();
-}
-
-void ShowTokenExchangeWebIdPermissionDialog(
-    content::WebContents* rp_web_contents,
-    const base::string16& idp_hostname,
-    const base::string16& rp_hostname,
-    content::IdentityRequestDialogController::TokenExchangeApprovalCallback
-        callback) {
-  NOTIMPLEMENTED();
-}
-
-WebIdSigninWindow* ShowWebIdSigninWindow(
-    content::WebContents* rp_web_contents,
-    content::WebContents* idp_web_contents,
-    const GURL& idp_signin_url,
-    content::IdentityRequestDialogController::IdProviderWindowClosedCallback
-        on_done) {
-  NOTIMPLEMENTED();
-  return nullptr;
-}
-
-void CloseWebIdSigninWindow(WebIdSigninWindow* window) {
-  NOTIMPLEMENTED();
-}
diff --git a/chrome/browser/ui/android/webid/webid_dialog_android.cc b/chrome/browser/ui/android/webid/webid_dialog_android.cc
new file mode 100644
index 0000000..ea4502e
--- /dev/null
+++ b/chrome/browser/ui/android/webid/webid_dialog_android.cc
@@ -0,0 +1,17 @@
+// Copyright 2020 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/webid/webid_dialog.h"
+
+// Stub implementations for Identity UI on Android.
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+// static
+WebIdDialog* WebIdDialog::Create(content::WebContents* rp_web_contents) {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
diff --git a/chrome/browser/ui/ash/cast_config_controller_media_router.cc b/chrome/browser/ui/ash/cast_config_controller_media_router.cc
index ee589ad1..226f80d 100644
--- a/chrome/browser/ui/ash/cast_config_controller_media_router.cc
+++ b/chrome/browser/ui/ash/cast_config_controller_media_router.cc
@@ -21,6 +21,7 @@
 #include "components/media_router/browser/media_router_factory.h"
 #include "components/media_router/browser/media_routes_observer.h"
 #include "components/media_router/browser/media_sinks_observer.h"
+#include "components/media_router/common/media_sink.h"
 #include "components/media_router/common/media_source.h"
 #include "components/user_manager/user_manager.h"
 
@@ -203,6 +204,13 @@
   devices_.clear();
 
   for (const media_router::MediaSink& sink : device_cache()->sinks()) {
+    // TODO(crbug.com/1154342): Remove this if-statement once the toolbar's Cast
+    // dialog no longer needs Meet sinks and they are disabled in the backend.
+    if (sink.IsMaybeCloudSink() &&
+        !base::FeatureList::IsEnabled(
+            media_router::kCastToMeetingFromCastDialog)) {
+      continue;
+    }
     ash::SinkAndRoute device;
     device.sink.id = sink.id();
     device.sink.name = sink.name();
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc b/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc
index 06859668..95d79e1 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc
@@ -148,38 +148,45 @@
 
   for (const HoldingSpaceItem* item : items) {
     if (item->file_path().empty()) {
+      holding_space_metrics::RecordItemFailureToLaunch(item->type());
       *complete_success_ptr = false;
       barrier_closure.Run();
       return;
     }
-    GetFileInfo(profile_, item->file_path(),
-                base::BindOnce(
-                    [](const base::WeakPtr<HoldingSpaceClientImpl>& weak_ptr,
-                       base::RepeatingClosure barrier_closure,
-                       bool* complete_success, const base::FilePath& file_path,
-                       const base::Optional<base::File::Info>& info) {
-                      if (!weak_ptr || !info.has_value()) {
-                        *complete_success = false;
+    GetFileInfo(
+        profile_, item->file_path(),
+        base::BindOnce(
+            [](const base::WeakPtr<HoldingSpaceClientImpl>& weak_ptr,
+               base::RepeatingClosure barrier_closure, bool* complete_success,
+               const base::FilePath& file_path, HoldingSpaceItem::Type type,
+               const base::Optional<base::File::Info>& info) {
+              if (!weak_ptr || !info.has_value()) {
+                holding_space_metrics::RecordItemFailureToLaunch(type);
+                *complete_success = false;
+                barrier_closure.Run();
+                return;
+              }
+              file_manager::util::OpenItem(
+                  weak_ptr->profile_, file_path,
+                  info.value().is_directory ? platform_util::OPEN_FOLDER
+                                            : platform_util::OPEN_FILE,
+                  base::BindOnce(
+                      [](base::RepeatingClosure barrier_closure,
+                         bool* complete_success, HoldingSpaceItem::Type type,
+                         platform_util::OpenOperationResult result) {
+                        const bool success =
+                            result == platform_util::OPEN_SUCCEEDED;
+                        if (!success) {
+                          holding_space_metrics::RecordItemFailureToLaunch(
+                              type);
+                          *complete_success = false;
+                        }
                         barrier_closure.Run();
-                        return;
-                      }
-                      file_manager::util::OpenItem(
-                          weak_ptr->profile_, file_path,
-                          info.value().is_directory ? platform_util::OPEN_FOLDER
-                                                    : platform_util::OPEN_FILE,
-                          base::BindOnce(
-                              [](base::RepeatingClosure barrier_closure,
-                                 bool* complete_success,
-                                 platform_util::OpenOperationResult result) {
-                                const bool success =
-                                    result == platform_util::OPEN_SUCCEEDED;
-                                *complete_success &= success;
-                                barrier_closure.Run();
-                              },
-                              barrier_closure, complete_success));
-                    },
-                    weak_factory_.GetWeakPtr(), barrier_closure,
-                    complete_success_ptr, item->file_path()));
+                      },
+                      barrier_closure, complete_success, type));
+            },
+            weak_factory_.GetWeakPtr(), barrier_closure, complete_success_ptr,
+            item->file_path(), item->type()));
   }
 }
 
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc b/chrome/browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc
index 7143f1ad..e71c36c 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc
@@ -170,6 +170,10 @@
   auto* holding_space_client = HoldingSpaceController::Get()->client();
   ASSERT_TRUE(holding_space_client);
 
+  // Verify no failures have yet been recorded.
+  base::HistogramTester histogram_tester;
+  histogram_tester.ExpectTotalCount("HoldingSpace.Item.FailureToLaunch", 0);
+
   {
     // Create a holding space item backed by a non-existing file.
     auto holding_space_item = HoldingSpaceItem::CreateFileBackedItem(
@@ -181,8 +185,14 @@
     base::RunLoop run_loop;
     holding_space_client->OpenItems(
         {holding_space_item.get()},
-        base::BindLambdaForTesting([&run_loop](bool success) {
+        base::BindLambdaForTesting([&](bool success) {
           EXPECT_FALSE(success);
+
+          // Verify the failure has been recorded.
+          histogram_tester.ExpectBucketCount(
+              "HoldingSpace.Item.FailureToLaunch", holding_space_item->type(),
+              1);
+
           run_loop.Quit();
         }));
     run_loop.Run();
@@ -203,6 +213,9 @@
         }));
     run_loop.Run();
   }
+
+  // Verify that only the expected failure was recorded.
+  histogram_tester.ExpectTotalCount("HoldingSpace.Item.FailureToLaunch", 1);
 }
 
 // Verifies that `HoldingSpaceClient::ShowItemInFolder()` works as intended when
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index a5b1111..20779584 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -39,7 +39,6 @@
 #include "chrome/browser/background/background_contents_service.h"
 #include "chrome/browser/background/background_contents_service_factory.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/content_settings/mixed_content_settings_tab_helper.h"
 #include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
@@ -483,9 +482,7 @@
   location_bar_model_ = std::make_unique<LocationBarModelImpl>(
       location_bar_model_delegate_.get(), content::kMaxURLDisplayChars);
 
-  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-                 content::Source<ThemeService>(
-                     ThemeServiceFactory::GetForProfile(profile_)));
+  ThemeServiceFactory::GetForProfile(profile_)->AddObserver(this);
 
   profile_pref_registrar_.Init(profile_->GetPrefs());
   profile_pref_registrar_.Add(
@@ -547,7 +544,7 @@
   // Stop observing notifications and destroy the tab monitor before continuing
   // with destruction. Profile destruction will unload extensions and reentrant
   // calls to Browser:: should be avoided while it is being torn down.
-  registrar_.RemoveAll();
+  ThemeServiceFactory::GetForProfile(profile_)->RemoveObserver(this);
   extension_browser_window_helper_.reset();
 
   // Like above, cancel delayed method calls into |this| to avoid re-entrancy.
@@ -2223,12 +2220,9 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// Browser, content::NotificationObserver implementation:
+// Browser, ThemeServiceObserver implementation:
 
-void Browser::Observe(int type,
-                      const content::NotificationSource& source,
-                      const content::NotificationDetails& details) {
-  DCHECK_EQ(chrome::NOTIFICATION_BROWSER_THEME_CHANGED, type);
+void Browser::OnThemeChanged() {
   window()->UserChangedTheme(BrowserThemeChangeType::kBrowserTheme);
 }
 
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index 1351b17..dc607e4 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -24,6 +24,7 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/devtools/devtools_toggle_action.h"
 #include "chrome/browser/profiles/scoped_profile_keep_alive.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "chrome/browser/ui/bookmarks/bookmark_bar.h"
 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper_observer.h"
 #include "chrome/browser/ui/browser_navigator.h"
@@ -43,8 +44,6 @@
 #include "components/sessions/core/session_id.h"
 #include "components/translate/content/browser/content_translate_driver.h"
 #include "components/zoom/zoom_observer.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/page_navigator.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
@@ -119,7 +118,7 @@
                 public BookmarkTabHelperObserver,
                 public zoom::ZoomObserver,
                 public content::PageNavigator,
-                public content::NotificationObserver,
+                public ThemeServiceObserver,
                 public translate::ContentTranslateDriver::TranslationObserver,
                 public ui::SelectFileDialog::Listener {
  public:
@@ -925,10 +924,8 @@
                                  void* params) override;
   void FileSelectionCanceled(void* params) override;
 
-  // Overridden from content::NotificationObserver:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  // Overridden from ThemeServiceObserver:
+  void OnThemeChanged() override;
 
   // Overridden from translate::ContentTranslateDriver::TranslationObserver:
   void OnIsPageTranslatedChanged(content::WebContents* source) override;
@@ -1097,8 +1094,6 @@
 
   // Data members /////////////////////////////////////////////////////////////
 
-  content::NotificationRegistrar registrar_;
-
   PrefChangeRegistrar profile_pref_registrar_;
 
   // This Browser's create params.
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 1759bddf..eece7b2 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -1061,11 +1061,14 @@
 }
 
 bool CanMoveActiveTabToReadLater(Browser* browser) {
-  GURL url =
-      GetURLToBookmark(browser->tab_strip_model()->GetActiveWebContents());
+  WebContents* web_contents =
+      browser->tab_strip_model()->GetActiveWebContents();
   ReadingListModel* model = GetReadingListModel(browser);
-  if (!model)
+  // |web_contents| can be nullptr if the last tab in the browser was closed
+  // but the browser wasn't closed yet. https://crbug.com/799668
+  if (!web_contents || !model)
     return false;
+  GURL url = GetURLToBookmark(web_contents);
   return model->IsUrlSupported(url);
 }
 
diff --git a/chrome/browser/ui/commander/entity_match.cc b/chrome/browser/ui/commander/entity_match.cc
index a1592e6b..5e926fc 100644
--- a/chrome/browser/ui/commander/entity_match.cc
+++ b/chrome/browser/ui/commander/entity_match.cc
@@ -52,7 +52,7 @@
                                               bool match_profile) {
   std::vector<WindowMatch> results;
   const BrowserList* browser_list = BrowserList::GetInstance();
-  double mru_score = 1.0;
+  double mru_score = .95;
   FuzzyFinder finder(input);
   std::vector<gfx::Range> ranges;
   for (BrowserList::const_reverse_iterator it =
@@ -91,12 +91,14 @@
   TabGroupModel* model = browser->tab_strip_model()->group_model();
   // For empty input, use this to preserve TabGroupModel's ordering, which is
   // arbitrary but still helpful to keep consistent across calls and surfaces.
-  double ordering_score = 1.0;
+  double ordering_score = .95;
   for (const tab_groups::TabGroupId& group_id : model->ListTabGroups()) {
     if (group_to_exclude == group_id)
       continue;
+    TabGroup* group = model->GetTabGroup(group_id);
+    const base::string16& group_title = group->visual_data()->title();
     const base::string16& title =
-        model->GetTabGroup(group_id)->visual_data()->title();
+        group_title.empty() ? group->GetContentString() : group_title;
     if (input.empty()) {
       GroupMatch match(group_id, title, ordering_score);
       results.push_back(std::move(match));
diff --git a/chrome/browser/ui/commander/tab_command_source.cc b/chrome/browser/ui/commander/tab_command_source.cc
index ea0cd2e..ef93240 100644
--- a/chrome/browser/ui/commander/tab_command_source.cc
+++ b/chrome/browser/ui/commander/tab_command_source.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/commander/tab_command_source.h"
 
+#include <numeric>
+
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/ui/accelerator_utils.h"
@@ -12,6 +14,7 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/commander/entity_match.h"
 #include "chrome/browser/ui/commander/fuzzy_finder.h"
+#include "chrome/browser/ui/tabs/tab_group_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/accelerators/accelerator.h"
@@ -33,6 +36,27 @@
   return nullptr;
 }
 
+// Returns the tab group that the currently selected tabs can *not* be moved to.
+// In practice, this is the tab group that *all* selected tabs belong to, if
+// any. In the common special case of single selection, this will return that
+// tab's group if it has one.
+base::Optional<tab_groups::TabGroupId> IneligibleGroupForSelected(
+    TabStripModel* tab_strip_model) {
+  base::Optional<tab_groups::TabGroupId> excluded_group = base::nullopt;
+  for (int index : tab_strip_model->selection_model().selected_indices()) {
+    auto group = tab_strip_model->GetTabGroupForTab(index);
+    if (group.has_value()) {
+      if (!excluded_group.has_value()) {
+        excluded_group = group;
+      } else if (group != excluded_group) {
+        // More than one group in the selection, so don't exclude anything.
+        return base::nullopt;
+      }
+    }
+  }
+  return excluded_group;
+}
+
 // Commands:
 
 // TODO(lgrey): If this command ships, upstream these to TabStripModel
@@ -98,6 +122,25 @@
                                    std::vector<int>(sel.begin(), sel.end()));
 }
 
+bool CanAddAllToNewGroup(const TabStripModel* model) {
+  return model->group_model()->ListTabGroups().size() == 0;
+}
+
+void AddAllToNewGroup(Browser* browser) {
+  std::vector<int> indices(browser->tab_strip_model()->count());
+  std::iota(indices.begin(), indices.end(), 0);
+  browser->tab_strip_model()->AddToNewGroup(indices);
+}
+
+void AddSelectedToNewGroup(Browser* browser) {
+  TabStripModel* model = browser->tab_strip_model();
+  const ui::ListSelectionModel::SelectedIndices& sel =
+      model->selection_model().selected_indices();
+  model->AddToNewGroup(std::vector<int>(sel.begin(), sel.end()));
+}
+
+// Multiphase commands:
+
 std::unique_ptr<CommandItem> CreateMoveTabsToWindowItem(
     Browser* source,
     const WindowMatch& match) {
@@ -118,6 +161,50 @@
   return results;
 }
 
+void AddTabsToGroup(base::WeakPtr<Browser> browser,
+                    tab_groups::TabGroupId group) {
+  if (!browser.get())
+    return;
+  const ui::ListSelectionModel::SelectedIndices& sel =
+      browser->tab_strip_model()->selection_model().selected_indices();
+  browser->tab_strip_model()->AddToExistingGroup(
+      std::vector<int>(sel.begin(), sel.end()), group);
+}
+
+CommandSource::CommandResults AddTabsToGroupCommandsForGroupsMatching(
+    Browser* browser,
+    const base::string16& input) {
+  CommandSource::CommandResults results;
+  TabStripModel* tab_strip_model = browser->tab_strip_model();
+  // Add "New Group", if appropriate. It should score highest with no input.
+  base::string16 new_group_title =
+      l10n_util::GetStringUTF16(IDS_TAB_CXMENU_SUBMENU_NEW_GROUP);
+  if (input.empty()) {
+    auto item = std::make_unique<CommandItem>(new_group_title, .99,
+                                              std::vector<gfx::Range>());
+    item->entity_type = CommandItem::Entity::kGroup;
+    item->command =
+        base::BindOnce(&AddSelectedToNewGroup, base::Unretained(browser));
+    results.push_back(std::move(item));
+  } else {
+    FuzzyFinder finder(input);
+    std::vector<gfx::Range> ranges;
+    if (auto item = ItemForTitle(new_group_title, finder, &ranges)) {
+      item->command =
+          base::BindOnce(&chrome::GroupTab, base::Unretained(browser));
+      results.push_back(std::move(item));
+    }
+  }
+  for (auto& match : GroupsMatchingInput(
+           browser, input, IneligibleGroupForSelected(tab_strip_model))) {
+    auto item = match.ToCommandItem();
+    item->command =
+        base::BindOnce(&AddTabsToGroup, browser->AsWeakPtr(), match.group);
+    results.push_back(std::move(item));
+  }
+  return results;
+}
+
 }  // namespace
 
 TabCommandSource::TabCommandSource() = default;
@@ -211,6 +298,29 @@
     }
   }
 
+  if (CanAddAllToNewGroup(tab_strip_model)) {
+    if (auto item =
+            ItemForTitle(u"Move all tabs to new group", finder, &ranges)) {
+      item->command =
+          base::BindOnce(&AddAllToNewGroup, base::Unretained(browser));
+      results.push_back(std::move(item));
+    }
+  }
+
+  if (!tab_strip_model->WillContextMenuGroup(tab_strip_model->active_index())) {
+    if (auto item = ItemForTitle(u"Ungroup tab", finder, &ranges)) {
+      item->command =
+          base::BindOnce(&chrome::GroupTab, base::Unretained(browser));
+      results.push_back(std::move(item));
+    }
+  }
+  if (auto item = ItemForTitle(u"Add tab to group...", finder, &ranges)) {
+    item->command = std::make_pair(
+        u"Add to group...",
+        base::BindRepeating(&AddTabsToGroupCommandsForGroupsMatching,
+                            base::Unretained(browser)));
+    results.push_back(std::move(item));
+  }
   return results;
 }
 
diff --git a/chrome/browser/ui/extensions/extension_settings_overridden_dialog_unittest.cc b/chrome/browser/ui/extensions/extension_settings_overridden_dialog_unittest.cc
index f5ff8dd8..d47501b 100644
--- a/chrome/browser/ui/extensions/extension_settings_overridden_dialog_unittest.cc
+++ b/chrome/browser/ui/extensions/extension_settings_overridden_dialog_unittest.cc
@@ -8,9 +8,11 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_base.h"
+#include "content/public/browser/storage_partition.h"
 #include "extensions/browser/disable_reason.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_util.h"
 #include "extensions/browser/uninstall_reason.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/manifest.h"
@@ -51,6 +53,13 @@
     scoped_refptr<const extensions::Extension> extension =
         extensions::ExtensionBuilder(name).SetLocation(location).Build();
     service()->AddExtension(extension.get());
+
+    // Make sure RegisterClient calls for storage are finished to avoid flaky
+    // crashes in QuotaManagerImpl::RegisterClient.
+    // TODO(crbug.com/1182630) : Remove this when 1182630 is fixed.
+    extensions::util::GetStoragePartitionForExtensionId(extension->id(),
+                                                        profile());
+    task_environment()->RunUntilIdle();
     return extension.get();
   }
 
diff --git a/chrome/browser/ui/prefs/prefs_tab_helper.cc b/chrome/browser/ui/prefs/prefs_tab_helper.cc
index 5b89ba6..b3c136b8 100644
--- a/chrome/browser/ui/prefs/prefs_tab_helper.cc
+++ b/chrome/browser/ui/prefs/prefs_tab_helper.cc
@@ -21,7 +21,6 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/font_pref_change_notifier_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/renderer_preferences_util.h"
@@ -37,8 +36,6 @@
 #include "components/prefs/pref_service.h"
 #include "components/proxy_config/proxy_config_pref_names.h"
 #include "components/strings/grit/components_locale_settings.h"
-#include "content/public/browser/notification_details.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/buildflags/buildflags.h"
@@ -321,15 +318,16 @@
   renderer_preferences_util::UpdateFromSystemSettings(render_prefs, profile_);
 
 #if defined(OS_POSIX) && !defined(OS_MAC) && !defined(OS_ANDROID)
-  registrar_.Add(this,
-                 chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-                 content::Source<ThemeService>(
-                     ThemeServiceFactory::GetForProfile(profile_)));
+  ThemeServiceFactory::GetForProfile(profile_)->AddObserver(this);
 #endif
 }
 
 PrefsTabHelper::~PrefsTabHelper() {
   PrefWatcher::Get(profile_)->UnregisterHelper(this);
+
+#if defined(OS_POSIX) && !defined(OS_MAC) && !defined(OS_ANDROID)
+  ThemeServiceFactory::GetForProfile(profile_)->RemoveObserver(this);
+#endif
 }
 
 // static
@@ -433,17 +431,8 @@
   PrefWatcherFactory::GetInstance();
 }
 
-void PrefsTabHelper::Observe(int type,
-                             const content::NotificationSource& source,
-                             const content::NotificationDetails& details) {
-#if defined(OS_POSIX) && !defined(OS_MAC) && !defined(OS_ANDROID)
-  if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) {
-    UpdateRendererPreferences();
-    return;
-  }
-#endif
-
-  NOTREACHED();
+void PrefsTabHelper::OnThemeChanged() {
+  UpdateRendererPreferences();
 }
 
 void PrefsTabHelper::UpdateWebPreferences() {
diff --git a/chrome/browser/ui/prefs/prefs_tab_helper.h b/chrome/browser/ui/prefs/prefs_tab_helper.h
index 1b846858..a781277 100644
--- a/chrome/browser/ui/prefs/prefs_tab_helper.h
+++ b/chrome/browser/ui/prefs/prefs_tab_helper.h
@@ -9,8 +9,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/font_pref_change_notifier.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 
 #if !defined(OS_ANDROID)
@@ -28,7 +27,7 @@
 }
 
 // Per-tab class to handle user preferences.
-class PrefsTabHelper : public content::NotificationObserver,
+class PrefsTabHelper : public ThemeServiceObserver,
                        public content::WebContentsUserData<PrefsTabHelper> {
  public:
   ~PrefsTabHelper() override;
@@ -46,10 +45,8 @@
   friend class content::WebContentsUserData<PrefsTabHelper>;
   friend class PrefWatcher;
 
-  // content::NotificationObserver overrides:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  // ThemeServiceObserver overrides:
+  void OnThemeChanged() override;
 
   // Update the WebContents's blink::RendererPreferences.
   void UpdateRendererPreferences();
@@ -61,7 +58,6 @@
 
   content::WebContents* web_contents_;
   Profile* profile_;
-  content::NotificationRegistrar registrar_;
 #if !defined(OS_ANDROID)
   base::CallbackListSubscription default_zoom_level_subscription_;
   FontPrefChangeNotifier::Registrar font_change_registrar_;
diff --git a/chrome/browser/ui/search/instant_theme_browsertest.cc b/chrome/browser/ui/search/instant_theme_browsertest.cc
index fae80b7..74e7079 100644
--- a/chrome/browser/ui/search/instant_theme_browsertest.cc
+++ b/chrome/browser/ui/search/instant_theme_browsertest.cc
@@ -5,12 +5,12 @@
 #include "base/feature_list.h"
 #include "base/macros.h"
 #include "build/build_config.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search/instant_service.h"
 #include "chrome/browser/search/instant_service_factory.h"
 #include "chrome/browser/search/instant_service_observer.h"
+#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/browser.h"
@@ -106,13 +106,11 @@
     size_t num_before = extensions::ExtensionRegistry::Get(profile())
                             ->enabled_extensions()
                             .size();
-    content::WindowedNotificationObserver theme_change_observer(
-        chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-        content::Source<ThemeService>(
-            ThemeServiceFactory::GetForProfile(profile())));
+    test::ThemeServiceChangedWaiter waiter(
+        ThemeServiceFactory::GetForProfile(profile()));
     ASSERT_TRUE(InstallExtensionWithUIAutoConfirm(
         theme_path, 1, extensions::ExtensionBrowserTest::browser()));
-    theme_change_observer.Wait();
+    waiter.WaitForThemeChanged();
     size_t num_after = extensions::ExtensionRegistry::Get(profile())
                            ->enabled_extensions()
                            .size();
diff --git a/chrome/browser/ui/search/local_ntp_backgrounds_browsertest.cc b/chrome/browser/ui/search/local_ntp_backgrounds_browsertest.cc
index 08f256b..fa17b40 100644
--- a/chrome/browser/ui/search/local_ntp_backgrounds_browsertest.cc
+++ b/chrome/browser/ui/search/local_ntp_backgrounds_browsertest.cc
@@ -6,12 +6,12 @@
 
 #include "base/files/file_util.h"
 #include "build/build_config.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/search/instant_service.h"
 #include "chrome/browser/search/instant_service_factory.h"
 #include "chrome/browser/search/instant_service_observer.h"
 #include "chrome/browser/search/search.h"
+#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/browser.h"
@@ -264,13 +264,11 @@
     size_t num_before = extensions::ExtensionRegistry::Get(profile())
                             ->enabled_extensions()
                             .size();
-    content::WindowedNotificationObserver theme_change_observer(
-        chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-        content::Source<ThemeService>(
-            ThemeServiceFactory::GetForProfile(profile())));
+    test::ThemeServiceChangedWaiter waiter(
+        ThemeServiceFactory::GetForProfile(profile()));
     ASSERT_TRUE(InstallExtensionWithUIAutoConfirm(
         theme_path, 1, extensions::ExtensionBrowserTest::browser()));
-    theme_change_observer.Wait();
+    waiter.WaitForThemeChanged();
     size_t num_after = extensions::ExtensionRegistry::Get(profile())
                            ->enabled_extensions()
                            .size();
diff --git a/chrome/browser/ui/tab_contents/chrome_web_contents_view_handle_drop_unittest.cc b/chrome/browser/ui/tab_contents/chrome_web_contents_view_handle_drop_unittest.cc
index acfde95..ec0c362 100644
--- a/chrome/browser/ui/tab_contents/chrome_web_contents_view_handle_drop_unittest.cc
+++ b/chrome/browser/ui/tab_contents/chrome_web_contents_view_handle_drop_unittest.cc
@@ -240,12 +240,15 @@
   base::FilePath path_1 = temp_dir.GetPath().AppendASCII("Foo.doc");
   base::FilePath path_2 = temp_dir.GetPath().AppendASCII("Bar.doc");
 
-  base::File file_1(path_1, base::File::FLAG_CREATE | base::File::FLAG_READ);
-  base::File file_2(path_2, base::File::FLAG_CREATE | base::File::FLAG_READ);
+  base::File file_1(path_1, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+  base::File file_2(path_2, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
 
   ASSERT_TRUE(file_1.IsValid());
   ASSERT_TRUE(file_2.IsValid());
 
+  file_1.WriteAtCurrentPos("foo content", 11);
+  file_2.WriteAtCurrentPos("bar content", 11);
+
   content::DropData data;
   data.filenames.emplace_back(path_1, path_1);
   data.filenames.emplace_back(path_2, path_2);
@@ -267,14 +270,18 @@
   base::FilePath path_2 = temp_dir.GetPath().AppendASCII("Bar.doc");
   base::FilePath path_3 = temp_dir.GetPath().AppendASCII("Baz.doc");
 
-  base::File file_1(path_1, base::File::FLAG_CREATE | base::File::FLAG_READ);
-  base::File file_2(path_2, base::File::FLAG_CREATE | base::File::FLAG_READ);
-  base::File file_3(path_3, base::File::FLAG_CREATE | base::File::FLAG_READ);
+  base::File file_1(path_1, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+  base::File file_2(path_2, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+  base::File file_3(path_3, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
 
   ASSERT_TRUE(file_1.IsValid());
   ASSERT_TRUE(file_2.IsValid());
   ASSERT_TRUE(file_3.IsValid());
 
+  file_1.WriteAtCurrentPos("foo content", 11);
+  file_2.WriteAtCurrentPos("bar content", 11);
+  file_3.WriteAtCurrentPos("baz content", 11);
+
   content::DropData data;
   data.filenames.emplace_back(temp_dir.GetPath(), temp_dir.GetPath());
 
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index ed0a7539..d2b7534 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -31,6 +31,7 @@
 #include "chrome/browser/file_system_access/file_system_access_tab_helper.h"
 #include "chrome/browser/history/history_tab_helper.h"
 #include "chrome/browser/history/top_sites_factory.h"
+#include "chrome/browser/history_clusters/history_clusters_tab_helper.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/lite_video/lite_video_observer.h"
 #include "chrome/browser/login_detection/login_detection_tab_helper.h"
@@ -270,6 +271,7 @@
   history::WebContentsTopSitesObserver::CreateForWebContents(
       web_contents, TopSitesFactory::GetForProfile(profile).get());
   HistoryTabHelper::CreateForWebContents(web_contents);
+  HistoryClustersTabHelper::CreateForWebContents(web_contents);
   InfoBarService::CreateForWebContents(web_contents);
   webapps::InstallableManager::CreateForWebContents(web_contents);
   PrefetchProxyTabHelper::CreateForWebContents(web_contents);
diff --git a/chrome/browser/ui/thumbnails/thumbnail_image.cc b/chrome/browser/ui/thumbnails/thumbnail_image.cc
index f889b13c..706b05f 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_image.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_image.cc
@@ -26,6 +26,11 @@
   thumbnail_->HandleSubscriptionDestroyed(this);
 }
 
+ThumbnailImage::CaptureReadiness ThumbnailImage::Delegate::GetCaptureReadiness()
+    const {
+  return CaptureReadiness::kNotReady;
+}
+
 ThumbnailImage::Delegate::~Delegate() {
   if (thumbnail_)
     thumbnail_->delegate_ = nullptr;
@@ -46,6 +51,11 @@
     delegate_->thumbnail_ = nullptr;
 }
 
+ThumbnailImage::CaptureReadiness ThumbnailImage::GetCaptureReadiness() const {
+  return delegate_ ? delegate_->GetCaptureReadiness()
+                   : CaptureReadiness::kNotReady;
+}
+
 std::unique_ptr<ThumbnailImage::Subscription> ThumbnailImage::Subscribe() {
   // Use explicit new since Subscription constructor is private.
   auto subscription =
diff --git a/chrome/browser/ui/thumbnails/thumbnail_image.h b/chrome/browser/ui/thumbnails/thumbnail_image.h
index 28d53adda..bbff6117 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_image.h
+++ b/chrome/browser/ui/thumbnails/thumbnail_image.h
@@ -28,6 +28,20 @@
 // uncompressed image to observers.
 class ThumbnailImage : public base::RefCounted<ThumbnailImage> {
  public:
+  // Describes the readiness of the source page for thumbnail capture.
+  enum class CaptureReadiness : int {
+    // The page is not ready for capturing.
+    kNotReady = 0,
+    // Thumbnails can be captured, but the page might change. Captured frames
+    // should not be used as the final thumbnail.
+    kReadyForInitialCapture,
+    // The page is fully loaded and a thumbnail can be captured that should be
+    // representative of the page's final state. Dynamic elements might not be
+    // in final position yet, but should settle fairly quickly (on the order of
+    // a few seconds).
+    kReadyForFinalCapture,
+  };
+
   // Smart pointer to reference-counted compressed image data; in this case
   // JPEG format.
   using CompressedThumbnailData =
@@ -93,6 +107,10 @@
     // useful to track when there are no observers. Default behavior is no-op.
     virtual void ThumbnailImageBeingObservedChanged(bool is_being_observed) = 0;
 
+    // Requests the backing tab's capture readiness from the delegate.
+    // The default implementation returns kUnknown.
+    virtual CaptureReadiness GetCaptureReadiness() const;
+
    protected:
     virtual ~Delegate();
 
@@ -105,6 +123,9 @@
 
   bool has_data() const { return data_.get(); }
 
+  // Gets the capture readiness of the backing tab.
+  CaptureReadiness GetCaptureReadiness() const;
+
   // Subscribe to thumbnail updates. See |Subscription| to set a
   // callback and conigure additional options.
   //
diff --git a/chrome/browser/ui/thumbnails/thumbnail_readiness_tracker.h b/chrome/browser/ui/thumbnails/thumbnail_readiness_tracker.h
index 27e92c9..323372a 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_readiness_tracker.h
+++ b/chrome/browser/ui/thumbnails/thumbnail_readiness_tracker.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_READINESS_TRACKER_H_
 
 #include "base/callback.h"
+#include "chrome/browser/ui/thumbnails/thumbnail_image.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 
@@ -13,19 +14,7 @@
 // navigation and loading state.
 class ThumbnailReadinessTracker : public content::WebContentsObserver {
  public:
-  enum class Readiness : int {
-    // The WebContents is not ready for capturing.
-    kNotReady = 0,
-    // Thumbnails can be captured, but the page might change. Don't use
-    // any captured frames as the final thumbnail.
-    kReadyForInitialCapture,
-    // A thumbnail can be captured that should be representative of the
-    // page's final state. For good measure, the client should still
-    // wait a few seconds to capture the final thumbnail since dynamic
-    // elements might not be in final position yet.
-    kReadyForFinalCapture,
-  };
-
+  using Readiness = ThumbnailImage::CaptureReadiness;
   using ReadinessChangeCallback = base::RepeatingCallback<void(Readiness)>;
 
   // |web_contents| should be a newly-created contents. If not, the
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
index 45d52d8..0d18321 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
@@ -140,7 +140,7 @@
   }
 
  private:
-  using PageReadiness = ThumbnailReadinessTracker::Readiness;
+  using CaptureReadinesss = ThumbnailImage::CaptureReadiness;
 
   // ThumbnailCaptureDriver::Client:
   void RequestCapture() override {
@@ -166,7 +166,7 @@
 
     visible_ = new_visible;
     capture_driver_.UpdatePageVisibility(visible_);
-    if (!visible_ && page_readiness_ != PageReadiness::kNotReady)
+    if (!visible_ && page_readiness_ != CaptureReadinesss::kNotReady)
       thumbnail_tab_helper_->CaptureThumbnailOnTabHidden();
   }
 
@@ -185,12 +185,16 @@
       web_contents()->GetController().LoadIfNecessary();
   }
 
-  void PageReadinessChanged(PageReadiness readiness) {
+  ThumbnailImage::CaptureReadiness GetCaptureReadiness() const override {
+    return page_readiness_;
+  }
+
+  void PageReadinessChanged(CaptureReadinesss readiness) {
     if (page_readiness_ == readiness)
       return;
     // If we transition back to a kNotReady state, clear any existing thumbnail,
     // as it will contain an old snapshot, possibly from a different domain.
-    if (readiness == PageReadiness::kNotReady)
+    if (readiness == CaptureReadinesss::kNotReady)
       thumbnail_tab_helper_->ClearData();
     page_readiness_ = readiness;
     capture_driver_.UpdatePageReadiness(readiness);
@@ -206,7 +210,7 @@
   bool visible_ = false;
 
   // Where we are in the page lifecycle.
-  PageReadiness page_readiness_ = PageReadiness::kNotReady;
+  CaptureReadinesss page_readiness_ = CaptureReadinesss::kNotReady;
 
   // Scoped request for video capture. Ensures we always decrement the counter
   // once per increment.
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_model_unittest.cc b/chrome/browser/ui/toolbar/toolbar_actions_model_unittest.cc
index 51b233d..a6727d21 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_model_unittest.cc
@@ -30,6 +30,7 @@
 #include "chrome/browser/ui/toolbar/test_toolbar_action_view_controller.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/test/base/testing_profile.h"
 #include "components/crx_file/id_util.h"
 #include "components/policy/core/common/policy_map.h"
 #include "components/prefs/scoped_user_pref_update.h"
@@ -39,6 +40,7 @@
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
+#include "extensions/browser/extension_util.h"
 #include "extensions/browser/pref_names.h"
 #include "extensions/browser/test_extension_registry_observer.h"
 #include "extensions/browser/uninstall_reason.h"
@@ -257,6 +259,13 @@
     return testing::AssertionFailure()
            << "Failed to install extension: " << extension->name();
   }
+  // Make sure RegisterClient calls for storage are finished to avoid flaky
+  // crashes in QuotaManagerImpl::RegisterClient.
+  // TODO(crbug.com/1182630) : Remove this when 1182630 is fixed.
+  extensions::util::GetStoragePartitionForExtensionId(extension->id(),
+                                                      profile());
+  task_environment()->RunUntilIdle();
+
   return testing::AssertionSuccess();
 }
 
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index 5b3b152..f8c57cd 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -114,6 +114,11 @@
 // https://crbug.com/928954
 const base::Feature kTabHoverCardImages{"TabHoverCardImages",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
+const char kTabHoverCardImagesNotReadyDelayParameterName[] =
+    "page_not_ready_delay";
+const char kTabHoverCardImagesLoadingDelayParameterName[] =
+    "page_loading_delay";
+const char kTabHoverCardImagesLoadedDelayParameterName[] = "page_loaded_delay";
 
 // Enables tab outlines in additional situations for accessibility.
 const base::Feature kTabOutlinesInLowContrastThemes{
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h
index fe56d942..6aeba6ce 100644
--- a/chrome/browser/ui/ui_features.h
+++ b/chrome/browser/ui/ui_features.h
@@ -68,6 +68,9 @@
 extern const char kTabHoverCardsFeatureParameterName[];
 
 extern const base::Feature kTabHoverCardImages;
+extern const char kTabHoverCardImagesNotReadyDelayParameterName[];
+extern const char kTabHoverCardImagesLoadingDelayParameterName[];
+extern const char kTabHoverCardImagesLoadedDelayParameterName[];
 
 extern const base::Feature kTabOutlinesInLowContrastThemes;
 
diff --git a/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc b/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
index c5fe5a8..dd473e8 100644
--- a/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
+++ b/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
@@ -527,6 +527,10 @@
   source.audio_share = audio_share_checkbox_ &&
                        audio_share_checkbox_->GetVisible() &&
                        audio_share_checkbox_->GetChecked();
+  if (source.audio_share &&
+      dialog_source_ == DialogSource::kGetCurrentBrowsingContextMedia) {
+    source.web_contents_id.disable_local_echo = true;
+  }
 
   if (source.type == DesktopMediaID::TYPE_WEB_CONTENTS) {
     // Activate the selected tab and bring the browser window for the selected
diff --git a/chrome/browser/ui/views/download/download_shelf_web_view.cc b/chrome/browser/ui/views/download/download_shelf_web_view.cc
index 5d999830..c4e7a2a 100644
--- a/chrome/browser/ui/views/download/download_shelf_web_view.cc
+++ b/chrome/browser/ui/views/download/download_shelf_web_view.cc
@@ -9,9 +9,9 @@
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/common/webui_url_constants.h"
 #include "ui/gfx/animation/animation.h"
 #include "ui/views/border.h"
-#include "url/url_constants.h"
 
 DownloadShelfWebView::DownloadShelfWebView(Browser* browser,
                                            BrowserView* parent)
@@ -21,8 +21,7 @@
       parent_(parent) {
   SetVisible(false);
 
-  // TODO: Replace with a new chrome::kChromeUIDownloadsBarURL.
-  LoadInitialURL(GURL(url::kAboutBlankURL));
+  LoadInitialURL(GURL(chrome::kChromeUIDownloadShelfURL));
 
   shelf_animation_.SetSlideDuration(base::TimeDelta::FromMilliseconds(
       gfx::Animation::ShouldRenderRichAnimation() ? 120 : 0));
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view_browsertest.cc b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view_browsertest.cc
index 59328940..8971ba6d7 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view_browsertest.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view_browsertest.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
+#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
@@ -73,18 +74,17 @@
 class ThemeChangeWaiter {
  public:
   explicit ThemeChangeWaiter(ThemeService* theme_service)
-      : theme_change_observer_(chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-                               content::Source<ThemeService>(theme_service)) {}
+      : waiter_(theme_service) {}
 
   ~ThemeChangeWaiter() {
-    theme_change_observer_.Wait();
+    waiter_.WaitForThemeChanged();
     // Theme changes propagate asynchronously in DesktopWindowTreeHostX11::
     // FrameTypeChanged(), so ensure all tasks are consumed.
     content::RunAllPendingInMessageLoop();
   }
 
  private:
-  content::WindowedNotificationObserver theme_change_observer_;
+  test::ThemeServiceChangedWaiter waiter_;
 
   DISALLOW_COPY_AND_ASSIGN(ThemeChangeWaiter);
 };
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
index f8ebb34..71cfeca 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
@@ -19,7 +19,6 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
@@ -32,6 +31,7 @@
 #include "chrome/browser/sync/test/integration/secondary_account_helper.h"
 #include "chrome/browser/sync/test/integration/status_change_checker.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
+#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/browser_finder.h"
@@ -56,7 +56,6 @@
 #include "components/sync/driver/sync_user_settings.h"
 #include "components/sync/test/fake_server/fake_server_network_resources.h"
 #include "content/public/browser/navigation_entry.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/test_utils.h"
@@ -195,12 +194,10 @@
 
   // The theme change destroys the avatar button. Make sure the profile chooser
   // widget doesn't try to reference a stale observer during its shutdown.
+  test::ThemeServiceChangedWaiter waiter(
+      ThemeServiceFactory::GetForProfile(profile()));
   InstallExtension(test_data_dir_.AppendASCII("theme"), 1);
-  content::WindowedNotificationObserver theme_change_observer(
-      chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-      content::Source<ThemeService>(
-          ThemeServiceFactory::GetForProfile(profile())));
-  theme_change_observer.Wait();
+  waiter.WaitForThemeChanged();
 
   EXPECT_TRUE(ProfileMenuView::IsShowing());
   profile_menu_view()->GetWidget()->Close();
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index 975817e0..ede8a936 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -802,6 +802,9 @@
 
     browser_view_->feature_promo_controller()->MaybeShowPromo(
         feature_engagement::kIPHDesktopTabGroupsNewGroupFeature);
+
+    browser_view_->feature_promo_controller()->MaybeShowPromo(
+        feature_engagement::kIPHReadingListEntryPointFeature);
   }
 }
 
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_interactive_uitest.cc
index 0ba4705..e9a0533 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_interactive_uitest.cc
@@ -36,6 +36,41 @@
 };
 
 #if defined(USE_AURA)
+
+namespace {
+
+// Similar to views::test::WidgetDestroyedWaiter but waiting after the widget
+// has been closed is a no-op rather than an error.
+class SafeWidgetDestroyedWaiter : public views::WidgetObserver {
+ public:
+  explicit SafeWidgetDestroyedWaiter(views::Widget* widget) {
+    observation_.Observe(widget);
+  }
+
+  // views::WidgetObserver:
+  void OnWidgetDestroyed(Widget* widget) override {
+    observation_.Reset();
+    if (!quit_closure_.is_null())
+      std::move(quit_closure_).Run();
+  }
+
+  void Wait() {
+    if (!observation_.IsObserving())
+      return;
+    DCHECK(quit_closure_.is_null());
+    quit_closure_ = run_loop_.QuitClosure();
+    run_loop_.Run();
+  }
+
+ private:
+  base::RunLoop run_loop_;
+  base::OnceClosure quit_closure_;
+  base::ScopedObservation<views::Widget, views::WidgetObserver> observation_{
+      this};
+};
+
+}  // namespace
+
 // Verify that the hover card is not visible when any key is pressed.
 IN_PROC_BROWSER_TEST_F(TabHoverCardBubbleViewInteractiveUiTest,
                        HoverCardHidesOnAnyKeyPressInSameWindow) {
@@ -49,10 +84,17 @@
   views::test::WidgetVisibleWaiter(widget).Wait();
   EXPECT_TRUE(widget->IsVisible());
 
+  // Verify that the hover card widget is destroyed sometime between now and
+  // when we check afterwards. Depending on platform, the destruction could be
+  // synchronous or asynchronous.
+  SafeWidgetDestroyedWaiter widget_destroyed_waiter(widget);
+
   EXPECT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_DOWN, false,
                                               false, false, false));
-  // Note, fade in/out animations are disabled for testing so there is no need
-  // to account for them here.
+
+  // Note, fade in/out animations are disabled for testing so this should be
+  // relatively quick.
+  widget_destroyed_waiter.Wait();
   EXPECT_EQ(nullptr, GetHoverCard(tab_strip));
 }
 #endif
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc b/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
index 42c0120..6cd4a48 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/callback_list.h"
 #include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
@@ -28,6 +29,36 @@
 
 namespace {
 
+base::TimeDelta GetPreviewImageCaptureDelay(
+    ThumbnailImage::CaptureReadiness readiness) {
+  int ms = 0;
+  switch (readiness) {
+    case ThumbnailImage::CaptureReadiness::kNotReady: {
+      static const int not_ready_delay = base::GetFieldTrialParamByFeatureAsInt(
+          features::kTabHoverCardImages,
+          features::kTabHoverCardImagesNotReadyDelayParameterName, 0);
+      ms = not_ready_delay;
+      break;
+    }
+    case ThumbnailImage::CaptureReadiness::kReadyForInitialCapture: {
+      static const int loading_delay = base::GetFieldTrialParamByFeatureAsInt(
+          features::kTabHoverCardImages,
+          features::kTabHoverCardImagesLoadingDelayParameterName, 0);
+      ms = loading_delay;
+      break;
+    }
+    case ThumbnailImage::CaptureReadiness::kReadyForFinalCapture: {
+      static const int loaded_delay = base::GetFieldTrialParamByFeatureAsInt(
+          features::kTabHoverCardImages,
+          features::kTabHoverCardImagesLoadedDelayParameterName, 0);
+      ms = loaded_delay;
+      break;
+    }
+  }
+  DCHECK_GE(ms, 0);
+  return base::TimeDelta::FromMilliseconds(ms);
+}
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 // UMA histograms that record animation smoothness for fade-in and fade-out
 // animations of tab hover card.
@@ -351,6 +382,7 @@
     // If the card was visible we need to update the card now, before any slide
     // or snap occurs.
     UpdateCardContent(tab);
+    MaybeStartThumbnailObservation(tab, /* is_initial_show */ false);
 
     // If widget is already visible and anchored to the correct tab we should
     // not try to reset the anchor view or reshow.
@@ -389,6 +421,7 @@
 
   CreateHoverCard(target_tab_);
   UpdateCardContent(target_tab_);
+  MaybeStartThumbnailObservation(target_tab_, is_initial);
 
   if (!is_initial || !UseAnimations()) {
     hover_card_->GetWidget()->Show();
@@ -480,10 +513,11 @@
     hover_card_->SetTextFade(0.0);
 
   hover_card_->UpdateCardContent(tab);
-  MaybeStartThumbnailObservation(tab);
 }
 
-void TabHoverCardController::MaybeStartThumbnailObservation(Tab* tab) {
+void TabHoverCardController::MaybeStartThumbnailObservation(
+    Tab* tab,
+    bool is_initial_show) {
   // If the preview image feature is not enabled, |thumbnail_observer_| will be
   // null.
   if (!thumbnail_observer_)
@@ -506,6 +540,38 @@
 
   // We're definitely going to wait for an image at some point.
   waiting_for_preview_ = true;
+  // For the first show there has already been a delay, so it's fine to ask for
+  // the image immediately; same is true if we already have a thumbnail.
+  //  Otherwise the delay is based on the capture readiness.
+  const base::TimeDelta capture_delay =
+      is_initial_show || thumbnail->has_data()
+          ? base::TimeDelta()
+          : GetPreviewImageCaptureDelay(thumbnail->GetCaptureReadiness());
+  if (capture_delay.is_zero()) {
+    thumbnail_observer_->Observe(thumbnail);
+  } else if (!delayed_show_timer_.IsRunning()) {
+    // Stop updating the preview image unless/until we re-enable capture.
+    thumbnail_observer_->Observe(nullptr);
+    hover_card_->ClearPreviewImage();
+    delayed_show_timer_.Start(
+        FROM_HERE, capture_delay,
+        base::BindOnce(&TabHoverCardController::StartThumbnailObservation,
+                       base::Unretained(this), tab));
+  }
+}
+
+void TabHoverCardController::StartThumbnailObservation(Tab* tab) {
+  if (tab != target_tab_)
+    return;
+
+  DCHECK(tab);
+  DCHECK(hover_card_);
+  DCHECK(waiting_for_preview_);
+
+  auto thumbnail = tab->data().thumbnail;
+  if (!thumbnail || thumbnail == thumbnail_observer_->current_image())
+    return;
+
   thumbnail_observer_->Observe(thumbnail);
 }
 
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller.h b/chrome/browser/ui/views/tabs/tab_hover_card_controller.h
index 18c3657..1026854 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller.h
@@ -59,7 +59,8 @@
 
   void CreateHoverCard(Tab* tab);
   void UpdateCardContent(Tab* tab);
-  void MaybeStartThumbnailObservation(Tab* tab);
+  void MaybeStartThumbnailObservation(Tab* tab, bool is_initial_show);
+  void StartThumbnailObservation(Tab* tab);
 
   void RecordTimeSinceLastSeenMetric(base::TimeDelta elapsed_time);
 
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_unittest.cc b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_unittest.cc
index edf11d6..0b6a3f61 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ui/views/toolbar/chrome_labs_bubble_view.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/about_flags.h"
@@ -18,6 +19,8 @@
 #include "components/flags_ui/flags_state.h"
 #include "components/version_info/channel.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event_utils.h"
+#include "ui/views/test/button_test_api.h"
 #include "ui/views/test/combobox_test_api.h"
 #include "ui/views/test/widget_test.h"
 
@@ -281,4 +284,20 @@
   EXPECT_FALSE(bubble_view->IsRestartPromptVisibleForTesting());
 }
 
+// TODO(crbug.com/1128855)
+#if !BUILDFLAG(IS_CHROMEOS_LACROS)
+TEST_F(ChromeLabsBubbleTest, ShowFeedbackPage) {
+  base::HistogramTester histogram_tester;
+
+  views::MdTextButton* feedback_button =
+      first_lab_item()->GetFeedbackButtonForTesting();
+  ui::MouseEvent e(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+                   ui::EventTimeForNow(), 0, 0);
+  views::test::ButtonTestApi test_api(feedback_button);
+  test_api.NotifyClick(e);
+
+  histogram_tester.ExpectTotalCount("Feedback.RequestSource", 1);
+}
+#endif
+
 #endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
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 40fa3b4..9734c10 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc
@@ -20,13 +20,19 @@
 #include "ui/views/metadata/metadata_impl_macros.h"
 
 namespace {
-void ShowFeedbackPage(Browser* browser, std::string feedback_category_name) {
-  chrome::ShowFeedbackPage(browser,
-                           chrome::FeedbackSource::kFeedbackSourceChromeLabs,
-                           std::string() /* description_template */,
-                           std::string() /* description_placeholder_text */,
-                           feedback_category_name /* category_tag */,
-                           std::string() /* extra_diagnostics */);
+
+void ShowFeedbackPage(Browser* browser,
+                      std::string feedback_category_name,
+                      base::string16 visible_name) {
+  chrome::ShowFeedbackPage(
+      browser, chrome::FeedbackSource::kFeedbackSourceChromeLabs,
+      /*description_template=*/std::string(),
+      /*description_placeholder_text=*/
+      l10n_util::GetStringFUTF8(
+          IDS_CHROMELABS_SEND_FEEDBACK_DESCRIPTION_PLACEHOLDER,
+          std::move(visible_name)),
+      /*category_tag=*/std::move(feedback_category_name),
+      /* extra_diagnostics=*/std::string());
 }
 
 }  // namespace
@@ -107,8 +113,10 @@
 
                    .SetSizeToLargestLabel(false),
                views::Builder<views::MdTextButton>()
+                   .CopyAddressTo(&feedback_button_)
                    .SetCallback(base::BindRepeating(&ShowFeedbackPage, browser,
-                                                    lab.feedback_category_name))
+                                                    lab.feedback_category_name,
+                                                    lab.visible_name))
                    .SetText(
                        l10n_util::GetStringUTF16(IDS_CHROMELABS_SEND_FEEDBACK))
                    .SetProperty(
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_item_view.h b/chrome/browser/ui/views/toolbar/chrome_labs_item_view.h
index fcdd1240..5b6c4c5 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_item_view.h
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_item_view.h
@@ -12,9 +12,12 @@
 #include "ui/views/view.h"
 
 class Browser;
-
 struct LabInfo;
 
+namespace views {
+class MdTextButton;
+}  // namespace views
+
 class ChromeLabsItemView : public views::View {
  public:
   METADATA_HEADER(ChromeLabsItemView);
@@ -32,6 +35,10 @@
     return lab_state_combobox_;
   }
 
+  views::MdTextButton* GetFeedbackButtonForTesting() {
+    return feedback_button_;
+  }
+
   const flags_ui::FeatureEntry* GetFeatureEntry();
 
  private:
@@ -39,6 +46,8 @@
   views::Combobox* lab_state_combobox_;
 
   const flags_ui::FeatureEntry* feature_entry_;
+
+  views::MdTextButton* feedback_button_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_CHROME_LABS_ITEM_VIEW_H_
diff --git a/chrome/browser/ui/views/user_education/feature_promo_dialog_browsertest.cc b/chrome/browser/ui/views/user_education/feature_promo_dialog_browsertest.cc
index 8fc77e94..ac8ace8a 100644
--- a/chrome/browser/ui/views/user_education/feature_promo_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/user_education/feature_promo_dialog_browsertest.cc
@@ -201,6 +201,12 @@
   ShowAndVerifyUi();
 }
 
+IN_PROC_BROWSER_TEST_F(FeaturePromoDialogReadLaterTest,
+                       InvokeUi_IPH_ReadingListEntryPoint) {
+  set_baseline("2749474");
+  ShowAndVerifyUi();
+}
+
 #if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
 
 // Need a separate fixture to override the feature flag.
diff --git a/chrome/browser/ui/views/user_education/feature_promo_registry.cc b/chrome/browser/ui/views/user_education/feature_promo_registry.cc
index 84b120c..fd2c8bae 100644
--- a/chrome/browser/ui/views/user_education/feature_promo_registry.cc
+++ b/chrome/browser/ui/views/user_education/feature_promo_registry.cc
@@ -57,6 +57,14 @@
   return browser_view->bookmark_bar()->read_later_button();
 }
 
+// kIPHReadingListEntryPointFeature:
+views::View* GetReadingListStarView(BrowserView* browser_view) {
+  return browser_view->toolbar()
+      ->location_bar()
+      ->page_action_icon_controller()
+      ->GetIconView(PageActionIconType::kBookmarkStar);
+}
+
 // kIPHReopenTabFeature:
 views::View* GetAppMenuButton(BrowserView* browser_view) {
   return browser_view->toolbar()->app_menu_button();
@@ -187,6 +195,16 @@
   }
 
   {
+    // kIPHReadingListEntryPointFeature:
+    FeaturePromoBubbleParams params;
+    params.body_string_specifier = IDS_READING_LIST_ENTRY_POINT_PROMO;
+    params.arrow = views::BubbleBorder::TOP_LEFT;
+
+    RegisterFeature(feature_engagement::kIPHReadingListEntryPointFeature,
+                    params, base::BindRepeating(GetReadingListStarView));
+  }
+
+  {
     // kIPHReopenTabFeature:
     FeaturePromoBubbleParams params;
     params.body_string_specifier = IDS_REOPEN_TAB_PROMO;
diff --git a/chrome/browser/ui/views/webid/webid_dialog_views.cc b/chrome/browser/ui/views/webid/webid_dialog_views.cc
new file mode 100644
index 0000000..faf3ae8
--- /dev/null
+++ b/chrome/browser/ui/views/webid/webid_dialog_views.cc
@@ -0,0 +1,175 @@
+// 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/webid/webid_dialog_views.h"
+
+#include <memory>
+#include "base/bind.h"
+#include "base/callback.h"
+#include "chrome/browser/ui/views/webid/webid_permission_view.h"
+#include "chrome/browser/ui/views/webid/webid_signin_page_view.h"
+#include "components/constrained_window/constrained_window_views.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/layout/flex_layout.h"
+#include "ui/views/layout/flex_layout_types.h"
+#include "ui/views/layout/layout_provider.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
+#include "ui/views/view.h"
+#include "ui/views/window/dialog_delegate.h"
+
+// Dimensions of the dialog itself.
+constexpr int kDialogMinWidth = 512;
+constexpr int kDialogHeight = 450;
+
+WebIdDialogViews::WebIdDialogViews(content::WebContents* rp_web_contents)
+    : WebIdDialogViews(rp_web_contents, nullptr) {}
+
+WebIdDialogViews::WebIdDialogViews(content::WebContents* rp_web_contents,
+                                   gfx::NativeView parent)
+    : WebIdDialog(rp_web_contents) {
+  // WebIdDialogViews is a WidgetDelegate, owned by its views::Widget. It
+  // is destroyed by `DeleteDelegate()` which is invoked by view
+  // hierarchy. The below check ensures this is true.
+  DCHECK(owned_by_widget());
+  set_parent_window(parent);
+}
+
+WebIdDialogViews::~WebIdDialogViews() = default;
+
+void WebIdDialogViews::ShowInitialPermission(const base::string16& idp_hostname,
+                                             const base::string16& rp_hostname,
+                                             PermissionCallback callback) {
+  state_ = State::kInitialPermission;
+  auto content_view = WebIdPermissionView::CreateForInitialPermission(
+      this, idp_hostname, rp_hostname);
+  permission_callback_ = std::move(callback);
+  SetContent(std::move(content_view));
+  ShowDialog();
+}
+
+void WebIdDialogViews::ShowTokenExchangePermission(
+    const base::string16& idp_hostname,
+    const base::string16& rp_hostname,
+    PermissionCallback callback) {
+  state_ = State::kTokenExchangePermission;
+  auto content_view = WebIdPermissionView::CreateForTokenExchangePermission(
+      this, idp_hostname, rp_hostname);
+  permission_callback_ = std::move(callback);
+  SetContent(std::move(content_view));
+  ShowDialog();
+}
+
+void WebIdDialogViews::ShowSigninPage(content::WebContents* idp_web_contents,
+                                      const GURL& idp_signin_url,
+                                      CloseCallback on_close) {
+  DCHECK(rp_web_contents());
+  state_ = State::kSignIn;
+
+  // TODO(majidvp): What happens if we are handling multiple concurrent WebId
+  // requests? At the moment we keep creating modal dialogs. This may be fine
+  // when these requests belong to different tabs but may break down if they
+  // are from the same tab or even share the same |initiator_web_contents|
+  // (e.g., two requests made from an iframe and its embedder frame). We need
+  // to investigate this to ensure we are providing appropriate UX.
+  // http://crbug.com/1141125
+  auto content_view = std::make_unique<SigninPageView>(
+      this, rp_web_contents(), idp_web_contents, idp_signin_url);
+
+  close_callback_ = std::move(on_close);
+  SetContent(std::move(content_view));
+  ShowDialog();
+}
+
+void WebIdDialogViews::CloseSigninPage() {
+  DCHECK_EQ(state_, State::kSignIn);
+  std::move(close_callback_).Run();
+  // Note that this does not close the dialog as we may want to show the token
+  // exchange permission still.
+}
+
+void WebIdDialogViews::ShowDialog() {
+  if (dialog_) {
+    dialog_->Show();
+    return;
+  }
+
+  SetModalType(ui::MODAL_TYPE_CHILD);
+  SetShowCloseButton(true);
+  set_margins(gfx::Insets());
+
+  auto width =
+      views::LayoutProvider::Get()->GetSnappedDialogWidth(kDialogMinWidth);
+  set_fixed_width(width);
+  SetPreferredSize({width, kDialogHeight});
+
+  auto* layout = SetLayoutManager(std::make_unique<views::FlexLayout>());
+  layout->SetOrientation(views::LayoutOrientation::kVertical)
+      .SetMainAxisAlignment(views::LayoutAlignment::kCenter)
+      .SetCrossAxisAlignment(views::LayoutAlignment::kStart);
+
+  SetCloseCallback(
+      base::BindOnce(&WebIdDialogViews::OnClose, base::Unretained(this)));
+
+  if (parent_window()) {
+    // To make testing easier we use the parent window if provided instead of
+    // showing the dialog with web contents as parent.
+    dialog_ = CreateBubble(this);
+    dialog_->Show();
+  } else {
+    // ShowWebModalDialogViews takes ownership of this, by way of the
+    // DeleteDelegate method.
+    dialog_ =
+        constrained_window::ShowWebModalDialogViews(this, rp_web_contents());
+  }
+}
+
+void WebIdDialogViews::SetContent(std::unique_ptr<views::View> content) {
+  // TODO(majidvp): Animate the switch between old and new content views.
+  if (content_)
+    RemoveChildViewT(content_);
+
+  content_ = AddChildView(std::move(content));
+}
+
+void WebIdDialogViews::OnClose() {
+  switch (state_) {
+    case State::kInitialPermission:
+    case State::kTokenExchangePermission:
+      if (permission_callback_) {
+        // The dialog has closed without the user expressing an explicit
+        // preference. The current permission request should be denied.
+        std::move(permission_callback_).Run(UserApproval::kDenied);
+      }
+      break;
+    case State::kSignIn:
+      if (close_callback_) {
+        // The IDP page has closed without the user completing the flow.
+        std::move(close_callback_).Run();
+      }
+      break;
+    case State::kUninitialized:
+      break;
+  }
+}
+
+bool WebIdDialogViews::Accept() {
+  std::move(permission_callback_).Run(UserApproval::kApproved);
+  // Accepting only closes the dialog once we are at token exchange state.
+  return state_ == State::kTokenExchangePermission;
+}
+
+bool WebIdDialogViews::Cancel() {
+  std::move(permission_callback_).Run(UserApproval::kDenied);
+  // Cancelling always closes the dialog.
+  return true;
+}
+
+BEGIN_METADATA(WebIdDialogViews, views::BubbleDialogDelegateView)
+END_METADATA
+
+// static
+WebIdDialog* WebIdDialog::Create(content::WebContents* rp_web_contents) {
+  return new WebIdDialogViews(rp_web_contents);
+}
diff --git a/chrome/browser/ui/views/webid/webid_dialog_views.h b/chrome/browser/ui/views/webid/webid_dialog_views.h
new file mode 100644
index 0000000..dd077b0
--- /dev/null
+++ b/chrome/browser/ui/views/webid/webid_dialog_views.h
@@ -0,0 +1,81 @@
+// 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_UI_VIEWS_WEBID_WEBID_DIALOG_VIEWS_H_
+#define CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_DIALOG_VIEWS_H_
+
+#include "chrome/browser/ui/webid/webid_dialog.h"
+#include "content/public/browser/identity_request_dialog_controller.h"
+#include "ui/views/bubble/bubble_dialog_delegate_view.h"
+#include "ui/views/metadata/metadata_header_macros.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+// Basic bubble dialog that is used in the WebID flow.
+//
+// It creates a dialog and changes the content of that dialog as user moves
+// through the WebID flow steps.
+class WebIdDialogViews : public WebIdDialog,
+                         public views::BubbleDialogDelegateView {
+ public:
+  METADATA_HEADER(WebIdDialogViews);
+  // Constructs a new dialog. The actual dialog widget will be modal to the
+  // |rp_web_contents| and is shownn using the
+  // |constrained_window::ShowWebModalDialogViews| machinery.
+  explicit WebIdDialogViews(content::WebContents* rp_web_contents);
+  // Constructs a new dialog. The actual dialog widget gets to be modal to the
+  // |parent| window. This bypasses constrained_window machinery making
+  // it easier to test.
+  WebIdDialogViews(content::WebContents* rp_web_contents,
+                   gfx::NativeView parent);
+  WebIdDialogViews(const WebIdDialogViews&) = delete;
+  WebIdDialogViews operator=(const WebIdDialogViews&) = delete;
+  ~WebIdDialogViews() override;
+
+  void ShowInitialPermission(const base::string16& idp_hostname,
+                             const base::string16& rp_hostname,
+                             PermissionCallback) override;
+  void ShowTokenExchangePermission(const base::string16& idp_hostname,
+                                   const base::string16& rp_hostname,
+                                   PermissionCallback) override;
+  void ShowSigninPage(content::WebContents* idp_web_contents,
+                      const GURL& idp_signin_url,
+                      CloseCallback) override;
+  void CloseSigninPage() override;
+
+ private:
+  // Shows the dialog and creates it if necessary.
+  void ShowDialog();
+  // Changes the content view of the dialog.
+  void SetContent(std::unique_ptr<views::View> content);
+
+  void OnClose();
+  bool Accept() override;
+  bool Cancel() override;
+
+  enum class State {
+    kUninitialized,
+    kInitialPermission,
+    kSignIn,
+    kTokenExchangePermission
+  };
+  // A simple state machine to keep track of where in flow we are.
+  State state_{State::kUninitialized};
+
+  PermissionCallback permission_callback_;
+  CloseCallback close_callback_;
+
+  // Dialog widget that shows the content. It is created and shown on the first
+  // step. It remains shown until user reaches the end of the flow or explicitly
+  // closes it.
+  views::Widget* dialog_{nullptr};
+  // The content that is currently shown.
+  views::View* content_{nullptr};
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_DIALOG_VIEWS_H_
diff --git a/chrome/browser/ui/views/webid/webid_dialog_views_unittest.cc b/chrome/browser/ui/views/webid/webid_dialog_views_unittest.cc
new file mode 100644
index 0000000..e68176e
--- /dev/null
+++ b/chrome/browser/ui/views/webid/webid_dialog_views_unittest.cc
@@ -0,0 +1,192 @@
+// 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/webid/webid_dialog_views.h"
+#include <memory>
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/test/bind.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/views/chrome_views_test_base.h"
+#include "content/public/browser/identity_request_dialog_controller.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
+#include "ui/views/test/dialog_test.h"
+#include "ui/views/test/widget_test.h"
+
+namespace {
+
+const base::string16 kRpHostname = base::ASCIIToUTF16("rp.example");
+const char* kRpUrl = "https://rp.example";
+const base::string16 kIdpHostname = base::ASCIIToUTF16("idp.example");
+const char* kIdpUrl = "https://idp.example";
+
+}  // namespace
+
+using UserApproval = content::IdentityRequestDialogController::UserApproval;
+using PermissionCallback =
+    content::IdentityRequestDialogController::InitialApprovalCallback;
+using CloseCallback =
+    content::IdentityRequestDialogController::IdProviderWindowClosedCallback;
+
+class WebIdDialogViewsTest : public ChromeViewsTestBase {
+ public:
+  void SetUp() override {
+    ChromeViewsTestBase::SetUp();
+    test_contents_ = CreateTestWebContents(GURL{kRpUrl});
+    parent_widget_ = CreateTestWidget();
+    dialog_ = new WebIdDialogViews(test_contents_.get(),
+                                   parent_widget_->GetNativeView());
+  }
+
+  void TearDown() override {
+    // Reset widget to close all windows before the final teardown.
+    parent_widget_.reset();
+    ChromeViewsTestBase::TearDown();
+  }
+
+  std::unique_ptr<content::WebContents> CreateTestWebContents(GURL url) {
+    auto instance = content::SiteInstance::Create(&profile_);
+    // Note that we don't initialize the RenderProcessHost. If that is needed
+    // use `content::SiteInstance::GetProcess()->Init()`.
+    auto contents = content::WebContentsTester::CreateTestWebContents(
+        &profile_, std::move(instance));
+    content::WebContentsTester::For(contents.get())->NavigateAndCommit(url);
+    return contents;
+  }
+
+  WebIdDialogViews* dialog() const { return dialog_; }
+  content::WebContents* web_contents() const { return test_contents_.get(); }
+
+ private:
+  std::unique_ptr<views::Widget> parent_widget_;
+  WebIdDialogViews* dialog_{nullptr};
+
+  // Following are all that we need to create a test web contents.
+  content::RenderViewHostTestEnabler test_render_host_enabler_;
+  TestingProfile profile_;
+  std::unique_ptr<content::WebContents> test_contents_;
+};
+
+TEST_F(WebIdDialogViewsTest, DialogButtonsState) {
+  // Initial permission should show two dialog buttons for OK and Cancel.
+  dialog()->ShowInitialPermission(kRpHostname, kIdpHostname, base::DoNothing());
+  EXPECT_EQ(dialog()->GetDialogButtons(),
+            ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
+  EXPECT_TRUE(dialog()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_CANCEL));
+  EXPECT_TRUE(dialog()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK));
+
+  // SignIn page should not show any buttons for OK and Cancel.
+  auto idp_contents = CreateTestWebContents(GURL{kIdpUrl});
+  dialog()->ShowSigninPage(idp_contents.get(), GURL{kIdpUrl},
+                           base::DoNothing());
+  EXPECT_EQ(dialog()->GetDialogButtons(), ui::DIALOG_BUTTON_NONE);
+
+  // Token exchange should show two dialog buttons for OK and Cancel.
+  dialog()->ShowTokenExchangePermission(kRpHostname, kIdpHostname,
+                                        base::DoNothing());
+  EXPECT_EQ(dialog()->GetDialogButtons(),
+            ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
+  EXPECT_TRUE(dialog()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_CANCEL));
+  EXPECT_TRUE(dialog()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK));
+}
+
+TEST_F(WebIdDialogViewsTest, ExplicitlyClosingSigninInvokesCallback) {
+  bool did_close = false;
+  auto on_close = base::BindLambdaForTesting([&]() { did_close = true; });
+  auto idp_contents = CreateTestWebContents(GURL{kIdpUrl});
+  dialog()->ShowSigninPage(idp_contents.get(), GURL{kIdpUrl},
+                           std::move(on_close));
+  EXPECT_FALSE(did_close);
+  dialog()->CloseSigninPage();
+  EXPECT_TRUE(did_close);
+}
+
+TEST_F(WebIdDialogViewsTest, ClosingDialogOnSigninInvokesCallback) {
+  bool did_close = false;
+  auto on_close = base::BindLambdaForTesting([&]() { did_close = true; });
+  auto idp_contents = CreateTestWebContents(GURL{kIdpUrl});
+  dialog()->ShowSigninPage(idp_contents.get(), GURL{kIdpUrl},
+                           std::move(on_close));
+  EXPECT_FALSE(did_close);
+  dialog()->Close();
+  EXPECT_TRUE(did_close);
+}
+
+TEST_F(WebIdDialogViewsTest, ClosingDialogOnInitialPermissionsRejectsCallback) {
+  UserApproval approval = UserApproval::kApproved;
+  auto on_permission_callback = base::BindLambdaForTesting(
+      [&](UserApproval result) { approval = result; });
+  dialog()->ShowInitialPermission(kRpHostname, kIdpHostname,
+                                  on_permission_callback);
+  dialog()->Close();
+  EXPECT_EQ(UserApproval::kDenied, approval);
+}
+
+TEST_F(WebIdDialogViewsTest, AcceptingOnInitialPermissionsAcceptsCallback) {
+  UserApproval approval = UserApproval::kDenied;
+  auto on_permission_callback = base::BindLambdaForTesting(
+      [&](UserApproval result) { approval = result; });
+  dialog()->ShowInitialPermission(kRpHostname, kIdpHostname,
+                                  on_permission_callback);
+  dialog()->AcceptDialog();
+  EXPECT_EQ(UserApproval::kApproved, approval);
+}
+
+TEST_F(WebIdDialogViewsTest, CancellingOnInitialPermissionsRejectsCallback) {
+  UserApproval approval = UserApproval::kApproved;
+  auto on_permission_callback = base::BindLambdaForTesting(
+      [&](UserApproval result) { approval = result; });
+  dialog()->ShowInitialPermission(kRpHostname, kIdpHostname,
+                                  on_permission_callback);
+  dialog()->CancelDialog();
+  EXPECT_EQ(UserApproval::kDenied, approval);
+}
+
+TEST_F(WebIdDialogViewsTest,
+       ClosingDialogOnTokenExchangePermissionsRejectsCallback) {
+  UserApproval approval = UserApproval::kApproved;
+  auto on_permission_callback = base::BindLambdaForTesting(
+      [&](UserApproval result) { approval = result; });
+  dialog()->ShowTokenExchangePermission(kRpHostname, kIdpHostname,
+                                        on_permission_callback);
+  dialog()->Close();
+  EXPECT_EQ(UserApproval::kDenied, approval);
+}
+
+TEST_F(WebIdDialogViewsTest,
+       AcceptingOnTokenExchangePermissionsAcceptsCallback) {
+  UserApproval approval = UserApproval::kDenied;
+  auto on_permission_callback = base::BindLambdaForTesting(
+      [&](UserApproval result) { approval = result; });
+  dialog()->ShowTokenExchangePermission(kRpHostname, kIdpHostname,
+                                        on_permission_callback);
+  dialog()->AcceptDialog();
+  EXPECT_EQ(UserApproval::kApproved, approval);
+}
+
+TEST_F(WebIdDialogViewsTest,
+       CancellingOnTokenExchangePermissionsRejectsCallback) {
+  UserApproval approval = UserApproval::kApproved;
+  auto on_permission_callback = base::BindLambdaForTesting(
+      [&](UserApproval result) { approval = result; });
+  dialog()->ShowInitialPermission(kRpHostname, kIdpHostname,
+                                  on_permission_callback);
+  dialog()->CancelDialog();
+  EXPECT_EQ(UserApproval::kDenied, approval);
+}
+
+TEST_F(WebIdDialogViewsTest, AcceptingOnTokenExchangePermissionsClosesDialog) {
+  dialog()->ShowTokenExchangePermission(kRpHostname, kIdpHostname,
+                                        base::DoNothing());
+
+  views::test::WidgetDestroyedWaiter waiter(dialog()->GetWidget());
+  dialog()->AcceptDialog();
+  waiter.Wait();
+  // This tests will timeout if dialog widget is not destroyed so not timing out
+  // is success.
+}
diff --git a/chrome/browser/ui/views/webid/webid_permission_dialog.h b/chrome/browser/ui/views/webid/webid_permission_dialog.h
deleted file mode 100644
index c80300b..0000000
--- a/chrome/browser/ui/views/webid/webid_permission_dialog.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_PERMISSION_DIALOG_H_
-#define CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_PERMISSION_DIALOG_H_
-
-#include "base/callback.h"
-#include "content/public/browser/identity_request_dialog_controller.h"
-#include "ui/views/bubble/bubble_dialog_delegate_view.h"
-
-namespace content {
-class WebContents;
-}  // namespace content
-
-// Basic permission dialog that is used to ask for user approval in various
-// WebID flows.
-//
-// It shows a message with two buttons to cancel/accept e.g.,
-// "Would you like to do X?  [Cancel]  [Continue]
-class WebIdPermissionDialog : public views::BubbleDialogDelegateView {
- public:
-  using UserApproval = content::IdentityRequestDialogController::UserApproval;
-  using Callback =
-      content::IdentityRequestDialogController::InitialApprovalCallback;
-
-  WebIdPermissionDialog(content::WebContents* rp_web_contents,
-                        std::unique_ptr<views::View> content,
-                        Callback callback);
-  ~WebIdPermissionDialog() override;
-  void Show();
-
- private:
-  bool Accept() override;
-  bool Cancel() override;
-
-  content::WebContents* rp_web_contents_;
-  Callback callback_;
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_PERMISSION_DIALOG_H_
diff --git a/chrome/browser/ui/views/webid/webid_permission_dialog.cc b/chrome/browser/ui/views/webid/webid_permission_view.cc
similarity index 69%
rename from chrome/browser/ui/views/webid/webid_permission_dialog.cc
rename to chrome/browser/ui/views/webid/webid_permission_view.cc
index 2aee3590..b8fe51d 100644
--- a/chrome/browser/ui/views/webid/webid_permission_dialog.cc
+++ b/chrome/browser/ui/views/webid/webid_permission_view.cc
@@ -2,26 +2,25 @@
 // 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/webid/webid_permission_dialog.h"
+#include "chrome/browser/ui/views/webid/webid_permission_view.h"
+
 #include <memory>
 
+#include "base/bind.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
-#include "chrome/browser/ui/webid/identity_dialogs.h"
 #include "chrome/grit/webid_resources.h"
-#include "components/constrained_window/constrained_window_views.h"
+#include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/range/range.h"
 #include "ui/gfx/text_constants.h"
-#include "ui/views/bubble/bubble_frame_view.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/controls/styled_label.h"
 #include "ui/views/layout/flex_layout.h"
 #include "ui/views/layout/flex_layout_types.h"
 #include "ui/views/layout/layout_provider.h"
 #include "ui/views/view_class_properties.h"
-#include "ui/views/window/dialog_delegate.h"
 
 // Dimensions of the dialog itself.
 constexpr int kDialogMinWidth = 512;
@@ -141,79 +140,45 @@
   return view;
 }
 
-WebIdPermissionDialog::WebIdPermissionDialog(
-    content::WebContents* rp_web_contents,
-    std::unique_ptr<views::View> content,
-    WebIdPermissionDialog::Callback callback)
-    : rp_web_contents_(rp_web_contents), callback_(std::move(callback)) {
-  DCHECK(callback_);
+// static
+std::unique_ptr<WebIdPermissionView>
+WebIdPermissionView::CreateForInitialPermission(
+    WebIdDialogViews* dialog,
+    const base::string16& idp_hostname,
+    const base::string16& rp_hostname) {
+  return std::make_unique<WebIdPermissionView>(
+      dialog, CreateInitialMessage(idp_hostname, rp_hostname));
+}
 
-  SetButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
-  SetModalType(ui::MODAL_TYPE_CHILD);
-  SetShowCloseButton(true);
-  set_margins(gfx::Insets());
+// static
+std::unique_ptr<WebIdPermissionView>
+WebIdPermissionView::CreateForTokenExchangePermission(
+    WebIdDialogViews* dialog,
+    const base::string16& idp_hostname,
+    const base::string16& rp_hostname) {
+  return std::make_unique<WebIdPermissionView>(
+      dialog, CreateTokenExchangeMessage(idp_hostname, rp_hostname));
+}
+
+WebIdPermissionView::WebIdPermissionView(WebIdDialogViews* dialog,
+                                         std::unique_ptr<views::View> content) {
+  dialog->SetButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
+  dialog->SetButtonEnabled(ui::DIALOG_BUTTON_OK, true);
+  dialog->SetButtonEnabled(ui::DIALOG_BUTTON_CANCEL, true);
 
   // TODO(majidvp): use localized strings
-  SetButtonLabel(ui::DIALOG_BUTTON_OK, base::ASCIIToUTF16("Continue"));
-  SetButtonLabel(ui::DIALOG_BUTTON_CANCEL, base::ASCIIToUTF16("Cancel"));
-
-  auto width =
-      views::LayoutProvider::Get()->GetSnappedDialogWidth(kDialogMinWidth);
-  set_fixed_width(width);
+  dialog->SetButtonLabel(ui::DIALOG_BUTTON_OK, base::ASCIIToUTF16("Continue"));
+  dialog->SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
+                         base::ASCIIToUTF16("Cancel"));
 
   auto* layout = SetLayoutManager(std::make_unique<views::FlexLayout>());
   layout->SetOrientation(views::LayoutOrientation::kVertical)
       .SetMainAxisAlignment(views::LayoutAlignment::kCenter)
       .SetCrossAxisAlignment(views::LayoutAlignment::kStart);
 
-  content->SetPreferredSize({width, kDialogHeight - kHeaderHeight});
+  content->SetPreferredSize({kDialogMinWidth, kDialogHeight - kHeaderHeight});
+
   AddChildView(std::move(content));
 }
 
-WebIdPermissionDialog::~WebIdPermissionDialog() {
-  if (callback_) {
-    // The dialog has closed without the user expressing an explicit
-    // preference. The current request should be denied.
-    std::move(callback_).Run(UserApproval::kDenied);
-  }
-}
-
-void WebIdPermissionDialog::Show() {
-  // ShowWebModalDialogViews takes ownership of this, by way of the
-  // DeleteDelegate method.
-  constrained_window::ShowWebModalDialogViews(this, rp_web_contents_);
-}
-
-bool WebIdPermissionDialog::Accept() {
-  std::move(callback_).Run(UserApproval::kApproved);
-  return true;
-}
-
-bool WebIdPermissionDialog::Cancel() {
-  std::move(callback_).Run(UserApproval::kDenied);
-  return true;
-}
-
-// Implement identity_dialogs.h functions
-
-void ShowInitialWebIdPermissionDialog(
-    content::WebContents* rp_web_contents,
-    const base::string16& idp_hostname,
-    const base::string16& rp_hostname,
-    WebIdPermissionDialog::Callback callback) {
-  auto content = CreateInitialMessage(idp_hostname, rp_hostname);
-  auto* dialog = new WebIdPermissionDialog(rp_web_contents, std::move(content),
-                                           std::move(callback));
-  dialog->Show();
-}
-
-void ShowTokenExchangeWebIdPermissionDialog(
-    content::WebContents* rp_web_contents,
-    const base::string16& idp_hostname,
-    const base::string16& rp_hostname,
-    WebIdPermissionDialog::Callback callback) {
-  auto content = CreateTokenExchangeMessage(idp_hostname, rp_hostname);
-  auto* dialog = new WebIdPermissionDialog(rp_web_contents, std::move(content),
-                                           std::move(callback));
-  dialog->Show();
-}
+WebIdPermissionView::~WebIdPermissionView() = default;
diff --git a/chrome/browser/ui/views/webid/webid_permission_view.h b/chrome/browser/ui/views/webid/webid_permission_view.h
new file mode 100644
index 0000000..f1c0ce2
--- /dev/null
+++ b/chrome/browser/ui/views/webid/webid_permission_view.h
@@ -0,0 +1,36 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_PERMISSION_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_PERMISSION_VIEW_H_
+
+#include <memory>
+#include "base/strings/string16.h"
+#include "chrome/browser/ui/views/webid/webid_dialog_views.h"
+#include "ui/views/view.h"
+
+// Basic permission dialog that is used to ask for user approval at different
+// points in the WebID flow.
+//
+// It shows a message with two buttons to cancel/accept e.g.,
+// "Would you like to do X?  [Cancel]  [Continue]
+class WebIdPermissionView : public views::View {
+ public:
+  static std::unique_ptr<WebIdPermissionView> CreateForInitialPermission(
+      WebIdDialogViews* dialog,
+      const base::string16& idp_hostname,
+      const base::string16& rp_hostname);
+  static std::unique_ptr<WebIdPermissionView> CreateForTokenExchangePermission(
+      WebIdDialogViews* dialog,
+      const base::string16& idp_hostname,
+      const base::string16& rp_hostname);
+
+  WebIdPermissionView(WebIdDialogViews* dialog,
+                      std::unique_ptr<views::View> content);
+  WebIdPermissionView(const WebIdPermissionView&) = delete;
+  WebIdPermissionView operator=(const WebIdPermissionView&) = delete;
+  ~WebIdPermissionView() override;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_PERMISSION_VIEW_H_
diff --git a/chrome/browser/ui/views/webid/webid_signin_page_view.cc b/chrome/browser/ui/views/webid/webid_signin_page_view.cc
new file mode 100644
index 0000000..d6d3d0b
--- /dev/null
+++ b/chrome/browser/ui/views/webid/webid_signin_page_view.cc
@@ -0,0 +1,179 @@
+// Copyright 2020 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/webid/webid_signin_page_view.h"
+
+#include "chrome/browser/ui/webid/identity_dialog_controller.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/views/border.h"
+#include "ui/views/bubble/bubble_dialog_delegate_view.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/separator.h"
+#include "ui/views/controls/webview/webview.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/layout/flex_layout.h"
+#include "ui/views/layout/flex_layout_types.h"
+#include "ui/views/metadata/metadata_header_macros.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
+#include "ui/views/window/dialog_delegate.h"
+
+// Dimensions of the dialog itself.
+constexpr int kDialogMinWidth = 512;
+constexpr int kDialogHeight = 450;
+// Dimension of the header.
+constexpr int kHeaderHeight = 50;
+
+// Creates the following UI:
+// +----------------+
+// |  Page Title    |
+// |  URL           |
+// +----------------+
+class TitleAndOriginView : public views::View {
+ public:
+  METADATA_HEADER(TitleAndOriginView);
+  TitleAndOriginView(const base::string16& page_title, const GURL& origin) {
+    // The logic here is mostly based on Payments UI used for
+    // `PaymentHandlerWebFlowViewController`.
+    constexpr int kLeftPadding = 5;
+    auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
+        views::BoxLayout::Orientation::kVertical,
+        gfx::Insets(0, kLeftPadding, 0, 0), 0 /* betweeen_child_spacing */));
+    layout->set_minimum_cross_axis_size(kDialogMinWidth);
+    layout->set_cross_axis_alignment(
+        views::BoxLayout::CrossAxisAlignment::kStart);
+
+    bool title_is_valid = !page_title.empty();
+    if (title_is_valid) {
+      auto* title_label = AddChildView(std::make_unique<views::Label>(
+          page_title, views::style::CONTEXT_DIALOG_TITLE));
+      title_label->SetFocusBehavior(
+          views::View::FocusBehavior::ACCESSIBLE_ONLY);
+    }
+
+    // We are not showing the schema since it is expected to always be
+    // `https://`.
+    CHECK(origin.SchemeIs(url::kHttpsScheme));
+    auto* origin_label = AddChildView(
+        std::make_unique<views::Label>(base::UTF8ToUTF16(origin.host())));
+    origin_label->SetElideBehavior(gfx::ELIDE_HEAD);
+    if (!title_is_valid) {
+      // Pad to keep header as the same height as when the page title is valid.
+      constexpr int kVerticalPadding = 10;
+      origin_label->SetBorder(
+          views::CreateEmptyBorder(kVerticalPadding, 0, kVerticalPadding, 0));
+    }
+  }
+  TitleAndOriginView(const TitleAndOriginView&) = delete;
+  TitleAndOriginView& operator=(const TitleAndOriginView&) = delete;
+  ~TitleAndOriginView() override = default;
+};
+
+BEGIN_METADATA(TitleAndOriginView, views::View)
+END_METADATA
+
+// The view for IDP sign in page.
+// It observes the loaded web contents to update header information as the load
+// progresses and in case it navigates.
+SigninPageView::SigninPageView(WebIdDialogViews* dialog,
+                               content::WebContents* initiator_web_contents,
+                               content::WebContents* idp_web_contents,
+                               const GURL& provider)
+    : dialog_(dialog),
+      initiator_web_contents_(initiator_web_contents),
+      web_view_(nullptr) {
+  // Create the following UI inside parent dialog:
+  // +----------------+
+  // |   Header view  |
+  // +--[separator]---+
+  // |                |
+  // |  Content View  |
+  // |                |
+  // +----------------+
+  //
+  // Currently the header view shows the title & URL using a
+  // `TitleAndOriginView` and content view shows the IDP sign in page using a
+  // `WebView`.
+  dialog_->SetButtons(ui::DIALOG_BUTTON_NONE);
+  dialog_->SetButtonEnabled(ui::DIALOG_BUTTON_OK, false);
+  dialog_->SetButtonEnabled(ui::DIALOG_BUTTON_CANCEL, false);
+
+  auto* layout = SetLayoutManager(std::make_unique<views::FlexLayout>());
+  layout->SetOrientation(views::LayoutOrientation::kVertical)
+      .SetMainAxisAlignment(views::LayoutAlignment::kCenter)
+      .SetCrossAxisAlignment(views::LayoutAlignment::kStart);
+
+  header_view_ = AddChildView(CreateHeaderView());
+  auto* separator = AddChildView(std::make_unique<views::Separator>());
+  separator->SetPreferredSize({kDialogMinWidth, views::Separator::kThickness});
+
+  web_view_ = AddChildView(CreateContentWebView(idp_web_contents, provider));
+  // Observe the webiew to react to URL and title changes.
+  Observe(web_view_->GetWebContents());
+  // Update the header once after setting up the web view as it uses the URL
+  // and title from the web view.
+  UpdateHeaderView();
+}
+
+std::unique_ptr<views::WebView> SigninPageView::CreateContentWebView(
+    content::WebContents* idp_web_contents,
+    const GURL& provider) {
+  auto web_view = std::make_unique<views::WebView>(
+      initiator_web_contents_->GetBrowserContext());
+
+  web_view->SetWebContents(idp_web_contents);
+  web_view->LoadInitialURL(provider);
+
+  // The webview must get an explicitly set height otherwise the layout
+  // doesn't make it fill its container. This is likely because it has no
+  // content at the time of first layout (nothing has loaded yet). Because of
+  // this, set it to. total_dialog_height - header_height. On the other hand,
+  // the width will be properly set so it can be 0 here.
+  web_view->SetPreferredSize({kDialogMinWidth, kDialogHeight - kHeaderHeight});
+
+  return web_view;
+}
+
+std::unique_ptr<views::View> SigninPageView::CreateHeaderView() {
+  auto header_view = std::make_unique<views::View>();
+  header_view->SetLayoutManager(std::make_unique<views::FillLayout>());
+  return header_view;
+}
+
+void SigninPageView::UpdateHeaderView() {
+  header_view_->RemoveAllChildViews(true);
+  header_view_->AddChildView(std::make_unique<TitleAndOriginView>(
+      web_view_->GetWebContents()->GetTitle(),
+      web_view_->GetWebContents()->GetVisibleURL().GetOrigin()));
+}
+
+// content::WebContentsObserver:
+void SigninPageView::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (navigation_handle->IsSameDocument())
+    return;
+
+  UpdateHeaderView();
+}
+
+void SigninPageView::LoadProgressChanged(double progress) {
+  // Dialog view comes with a neat progressbar so we can use that to show the
+  // progress.
+  if (progress >= 1) {
+    // hide the progress bar
+    dialog_->GetBubbleFrameView()->SetProgress(base::nullopt);
+    return;
+  }
+  dialog_->GetBubbleFrameView()->SetProgress(progress);
+}
+
+void SigninPageView::TitleWasSet(content::NavigationEntry* entry) {
+  UpdateHeaderView();
+}
+
+BEGIN_METADATA(SigninPageView, views::View)
+END_METADATA
diff --git a/chrome/browser/ui/views/webid/webid_signin_page_view.h b/chrome/browser/ui/views/webid/webid_signin_page_view.h
new file mode 100644
index 0000000..fe7f823
--- /dev/null
+++ b/chrome/browser/ui/views/webid/webid_signin_page_view.h
@@ -0,0 +1,59 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_SIGNIN_PAGE_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_SIGNIN_PAGE_VIEW_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "chrome/browser/ui/views/webid/webid_dialog_views.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "ui/views/metadata/metadata_header_macros.h"
+#include "ui/views/view.h"
+
+class GURL;
+
+namespace views {
+class WebView;
+}  // namespace views
+
+// The WebIdSigninWindow loads IDP sign-in page in a modal allowing user to
+// sign in. The modal may be closed by user or once IDP sign-in page has
+// completed its process and have called the appropriate JS callback.
+class SigninPageView : public views::View, public content::WebContentsObserver {
+ public:
+  METADATA_HEADER(SigninPageView);
+  SigninPageView(WebIdDialogViews* dialog,
+                 content::WebContents* initiator_web_contents,
+                 content::WebContents* idp_web_contents,
+                 const GURL& provider);
+  ~SigninPageView() override = default;
+
+  // content::WebContentsObserver:
+  void DidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void LoadProgressChanged(double progress) override;
+  void TitleWasSet(content::NavigationEntry* entry) override;
+
+ private:
+  std::unique_ptr<views::WebView> CreateContentWebView(
+      content::WebContents* idp_web_contents,
+      const GURL& provider);
+  std::unique_ptr<views::View> CreateHeaderView();
+
+  void UpdateHeaderView();
+
+  // The dialog that is hosting this view.
+  WebIdDialogViews* dialog_;
+
+  content::WebContents* initiator_web_contents_;
+  // The header of the dialog, owned by the view hierarchy.
+  views::View* header_view_;
+  // The contents of the dialog, owned by the view hierarchy.
+  views::WebView* web_view_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_SIGNIN_PAGE_VIEW_H_
diff --git a/chrome/browser/ui/views/webid/webid_signin_window.cc b/chrome/browser/ui/views/webid/webid_signin_window.cc
deleted file mode 100644
index 53921ef..0000000
--- a/chrome/browser/ui/views/webid/webid_signin_window.cc
+++ /dev/null
@@ -1,254 +0,0 @@
-// Copyright 2020 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/webid/webid_signin_window.h"
-
-#include "chrome/browser/ui/webid/identity_dialog_controller.h"
-#include "chrome/browser/ui/webid/identity_dialogs.h"
-#include "components/constrained_window/constrained_window_views.h"
-#include "components/web_modal/web_contents_modal_dialog_manager.h"
-#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
-#include "content/public/browser/navigation_entry.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/web_contents.h"
-#include "ui/views/border.h"
-#include "ui/views/bubble/bubble_dialog_delegate_view.h"
-#include "ui/views/bubble/bubble_frame_view.h"
-#include "ui/views/controls/label.h"
-#include "ui/views/controls/separator.h"
-#include "ui/views/controls/webview/webview.h"
-#include "ui/views/layout/fill_layout.h"
-#include "ui/views/layout/flex_layout.h"
-#include "ui/views/layout/flex_layout_types.h"
-#include "ui/views/layout/grid_layout.h"
-#include "ui/views/metadata/metadata_header_macros.h"
-#include "ui/views/metadata/metadata_impl_macros.h"
-#include "ui/views/window/dialog_delegate.h"
-
-#include "ui/views/layout/box_layout.h"
-
-// Dimensions of the dialog itself.
-constexpr int kDialogMinWidth = 512;
-constexpr int kDialogHeight = 450;
-// Dimension of the header.
-constexpr int kHeaderHeight = 50;
-
-// Creates the following UI:
-// +----------------+
-// |  Page Title    |
-// |  URL           |
-// +----------------+
-class TitleAndOriginView : public views::View {
- public:
-  METADATA_HEADER(TitleAndOriginView);
-  TitleAndOriginView(const base::string16& page_title, const GURL& origin) {
-    // The logic here is mostly based on Payments UI used for
-    // `PaymentHandlerWebFlowViewController`.
-    constexpr int kLeftPadding = 5;
-    auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
-        views::BoxLayout::Orientation::kVertical,
-        gfx::Insets(0, kLeftPadding, 0, 0), 0 /* betweeen_child_spacing */));
-    layout->set_minimum_cross_axis_size(kDialogMinWidth);
-    layout->set_cross_axis_alignment(
-        views::BoxLayout::CrossAxisAlignment::kStart);
-
-    bool title_is_valid = !page_title.empty();
-    if (title_is_valid) {
-      auto* title_label = AddChildView(std::make_unique<views::Label>(
-          page_title, views::style::CONTEXT_DIALOG_TITLE));
-      title_label->SetFocusBehavior(
-          views::View::FocusBehavior::ACCESSIBLE_ONLY);
-    }
-
-    // We are not showing the schema since it is expected to always be
-    // `https://`.
-    CHECK(origin.SchemeIs(url::kHttpsScheme));
-    auto* origin_label = AddChildView(
-        std::make_unique<views::Label>(base::UTF8ToUTF16(origin.host())));
-    origin_label->SetElideBehavior(gfx::ELIDE_HEAD);
-    if (!title_is_valid) {
-      // Pad to keep header as the same height as when the page title is valid.
-      constexpr int kVerticalPadding = 10;
-      origin_label->SetBorder(
-          views::CreateEmptyBorder(kVerticalPadding, 0, kVerticalPadding, 0));
-    }
-  }
-  TitleAndOriginView(const TitleAndOriginView&) = delete;
-  TitleAndOriginView& operator=(const TitleAndOriginView&) = delete;
-  ~TitleAndOriginView() override = default;
-};
-
-BEGIN_METADATA(TitleAndOriginView, views::View)
-END_METADATA
-
-// The view for IDP sign in page.
-// It observes the loaded web contents to update header information as the load
-// progresses and in case it navigates.
-class SigninDialogView : public views::BubbleDialogDelegateView,
-                         public content::WebContentsObserver {
- public:
-  METADATA_HEADER(SigninDialogView);
-  SigninDialogView(content::WebContents* initiator_web_contents,
-                   content::WebContents* idp_web_contents,
-                   const GURL& provider)
-      : initiator_web_contents_(initiator_web_contents), web_view_(nullptr) {
-    // Create the following UI:
-    // +----------------+
-    // |   Header view  |
-    // +--[separator]---+
-    // |                |
-    // |  Content View  |
-    // |                |
-    // +----------------+
-    //
-    // Currently the header view shows the title & URL using a
-    // `TitleAndOriginView` and content view shows the IDP sign in page using a
-    // `WebView`.
-    DialogDelegate::SetButtons(ui::DIALOG_BUTTON_NONE);
-    SetModalType(ui::MODAL_TYPE_CHILD);
-    SetShowCloseButton(true);
-    set_margins(gfx::Insets());
-
-    auto* layout = SetLayoutManager(std::make_unique<views::FlexLayout>());
-    layout->SetOrientation(views::LayoutOrientation::kVertical)
-        .SetMainAxisAlignment(views::LayoutAlignment::kCenter)
-        .SetCrossAxisAlignment(views::LayoutAlignment::kStart);
-
-    header_view_ = AddChildView(CreateHeaderView());
-    auto* separator = AddChildView(std::make_unique<views::Separator>());
-    separator->SetPreferredSize(
-        {kDialogMinWidth, views::Separator::kThickness});
-
-    web_view_ = AddChildView(CreateContentWebView(idp_web_contents, provider));
-    // Observe the webiew to react to URL and title changes.
-    Observe(web_view_->GetWebContents());
-    // Update the header once after setting up the web view as it uses the URL
-    // and title from the web view.
-    UpdateHeaderView();
-
-    SetInitiallyFocusedView(web_view_);
-  }
-
-  std::unique_ptr<views::WebView> CreateContentWebView(
-      content::WebContents* idp_web_contents,
-      const GURL& provider) {
-    auto web_view = std::make_unique<views::WebView>(
-        initiator_web_contents_->GetBrowserContext());
-
-    web_view->SetWebContents(idp_web_contents);
-    web_view->LoadInitialURL(provider);
-
-    // The webview must get an explicitly set height otherwise the layout
-    // doesn't make it fill its container. This is likely because it has no
-    // content at the time of first layout (nothing has loaded yet). Because of
-    // this, set it to. total_dialog_height - header_height. On the other hand,
-    // the width will be properly set so it can be 0 here.
-    web_view->SetPreferredSize(
-        {kDialogMinWidth, kDialogHeight - kHeaderHeight});
-
-    return web_view;
-  }
-
-  std::unique_ptr<views::View> CreateHeaderView() {
-    auto header_view = std::make_unique<views::View>();
-    header_view->SetLayoutManager(std::make_unique<views::FillLayout>());
-    return header_view;
-  }
-
-  void UpdateHeaderView() {
-    header_view_->RemoveAllChildViews(true);
-    header_view_->AddChildView(std::make_unique<TitleAndOriginView>(
-        web_view_->GetWebContents()->GetTitle(),
-        web_view_->GetWebContents()->GetVisibleURL().GetOrigin()));
-  }
-
-  views::Widget* Show() {
-    // ShowWebModalDialogViews takes ownership of this, by way of the
-    // DeleteDelegate method.
-    return constrained_window::ShowWebModalDialogViews(this,
-                                                       initiator_web_contents_);
-  }
-
-  // content::WebContentsObserver:
-  void DidFinishNavigation(
-      content::NavigationHandle* navigation_handle) override {
-    if (navigation_handle->IsSameDocument())
-      return;
-
-    UpdateHeaderView();
-  }
-
-  void LoadProgressChanged(double progress) override {
-    // Dialog view comes with a neat progressbar so we can use that to show the
-    // progress.
-    if (progress >= 1) {
-      // hide the progress bar
-      GetBubbleFrameView()->SetProgress(base::nullopt);
-      return;
-    }
-    GetBubbleFrameView()->SetProgress(progress);
-  }
-
-  void TitleWasSet(content::NavigationEntry* entry) override {
-    UpdateHeaderView();
-  }
-
- private:
-  content::WebContents* initiator_web_contents_;
-  // The header of the dialog, owned by the view hierarchy.
-  views::View* header_view_;
-  // The contents of the dialog, owned by the view hierarchy.
-  views::WebView* web_view_;
-};
-
-BEGIN_METADATA(SigninDialogView, views::BubbleDialogDelegateView)
-END_METADATA
-
-WebIdSigninWindow::WebIdSigninWindow(
-    content::WebContents* initiator_web_contents,
-    content::WebContents* idp_web_contents,
-    const GURL& provider,
-    IdProviderWindowClosedCallback on_done) {
-  // TODO(majidvp): What happens if we are handling multiple concurrent WebId
-  // requests? At the moment we keep creating modal dialogs. This may be fine
-  // when these requests belong to different tabs but may break down if they are
-  // from the same tab or even share the same |initiator_web_contents| (e.g.,
-  // two requests made from an iframe and its embedder frame). We need to
-  // investigate this to ensure we are providing appropriate UX.
-  // http://crbug.com/1141125
-  auto* dialog =
-      new SigninDialogView(initiator_web_contents, idp_web_contents, provider);
-
-  // Set close callback to also call on_done. This ensures that if user closes
-  // the IDP window the caller promise is rejected accordingly.
-  dialog->SetCloseCallback(std::move(on_done));
-
-  // SigninDialogView is a WidgetDelegate, owned by its views::Widget. It is
-  // destroyed by `DeleteDelegate()` which is invoked by view hierarchy. Once
-  // modal is deleted we should delete the window class as well.
-  dialog->RegisterDeleteDelegateCallback(
-      base::BindOnce([](WebIdSigninWindow* window) { delete window; },
-                     base::Unretained(this)));
-
-  dialog_ = dialog->Show();
-}
-
-void WebIdSigninWindow::Close() {
-  dialog_->Close();
-}
-
-WebIdSigninWindow::~WebIdSigninWindow() = default;
-
-WebIdSigninWindow* ShowWebIdSigninWindow(
-    content::WebContents* initiator_web_contents,
-    content::WebContents* idp_web_contents,
-    const GURL& provider,
-    IdProviderWindowClosedCallback on_done) {
-  return new WebIdSigninWindow(initiator_web_contents, idp_web_contents,
-                               provider, std::move(on_done));
-}
-
-void CloseWebIdSigninWindow(WebIdSigninWindow* window) {
-  window->Close();
-}
diff --git a/chrome/browser/ui/views/webid/webid_signin_window.h b/chrome/browser/ui/views/webid/webid_signin_window.h
deleted file mode 100644
index 844d134..0000000
--- a/chrome/browser/ui/views/webid/webid_signin_window.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_SIGNIN_WINDOW_H_
-#define CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_SIGNIN_WINDOW_H_
-
-#include <memory>
-#include <string>
-
-#include "base/callback.h"
-
-class GURL;
-
-namespace content {
-class WebContents;
-}
-
-namespace views {
-class Widget;
-}
-
-// The WebIdSigninWindow loads Idp sign-in page in a modal allowing user to
-// sign in. The modal may be closed by user or once Idp sign-in page has
-// completed its process and have called the appropriate JS callback.
-class WebIdSigninWindow {
- public:
-  // Calls the  provided callback when IDP has provided an id_token with the
-  // id_token a its argument, or when window is closed by user with an empty
-  // string as its argument.
-  WebIdSigninWindow(content::WebContents* initiator_web_contents,
-                    content::WebContents* idp_web_contents,
-                    const GURL& provider,
-                    base::OnceCallback<void()> on_done);
-  WebIdSigninWindow(const WebIdSigninWindow&) = delete;
-  WebIdSigninWindow& operator=(const WebIdSigninWindow&) = delete;
-
-  void Close();
-
- private:
-  // This class manages its own lifetime which is controlled by the view
-  // hierarchy. Once modal is deleted, this gets deleted as well.
-  ~WebIdSigninWindow();
-
-  views::Widget* dialog_;
-};
-#endif  // CHROME_BROWSER_UI_VIEWS_WEBID_WEBID_SIGNIN_WINDOW_H_
diff --git a/chrome/browser/ui/webid/identity_dialog_controller.cc b/chrome/browser/ui/webid/identity_dialog_controller.cc
index 787d04bf..39327ed 100644
--- a/chrome/browser/ui/webid/identity_dialog_controller.cc
+++ b/chrome/browser/ui/webid/identity_dialog_controller.cc
@@ -4,9 +4,11 @@
 
 #include "chrome/browser/ui/webid/identity_dialog_controller.h"
 
+#include <memory>
+
 #include "base/strings/strcat.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/ui/webid/identity_dialogs.h"
+#include "chrome/browser/ui/webid/webid_dialog.h"
 #include "components/infobars/core/infobar.h"
 #include "url/gurl.h"
 
@@ -18,6 +20,9 @@
     content::WebContents* rp_web_contents,
     const GURL& idp_url,
     InitialApprovalCallback callback) {
+  DCHECK(!view_);
+  view_ = WebIdDialog::Create(rp_web_contents);
+
   // The WebContents should be that of RP page to make sure info bar is shown on
   // the RP page.
 
@@ -27,8 +32,9 @@
   auto rp_hostname =
       base::UTF8ToUTF16(rp_web_contents->GetVisibleURL().GetOrigin().host());
 
-  ShowInitialWebIdPermissionDialog(rp_web_contents, idp_hostname, rp_hostname,
-                                   std::move(callback));
+  // TODO(majidvp): remove rp_web_contents from args
+  DCHECK_EQ(view_->rp_web_contents(), rp_web_contents);
+  view_->ShowInitialPermission(idp_hostname, rp_hostname, std::move(callback));
 }
 
 void IdentityDialogController::ShowIdProviderWindow(
@@ -36,15 +42,17 @@
     content::WebContents* idp_web_contents,
     const GURL& idp_signin_url,
     IdProviderWindowClosedCallback callback) {
-  signin_window_ = ShowWebIdSigninWindow(rp_web_contents, idp_web_contents,
-                                         idp_signin_url, std::move(callback));
+  // TODO(majidvp): remove rp_web_contents from args
+  DCHECK_EQ(view_->rp_web_contents(), rp_web_contents);
+
+  view_->ShowSigninPage(idp_web_contents, idp_signin_url, std::move(callback));
 }
 
 void IdentityDialogController::CloseIdProviderWindow() {
   // TODO(majidvp): This may race with user closing the signin window directly.
   // So we should not really check the signin_window_ instead we should setup
   // the on_close callback here here and check that to avoid lifetime issues.
-  if (!signin_window_)
+  if (!view_)
     return;
 
   // Note that this leads to the window closed callback being run. If the
@@ -54,7 +62,7 @@
   // TODO(kenrb, majidvp): Not knowing whether this object will be destroyed
   // or not during the callback is problematic. We have to rethink the
   // lifetimes.
-  CloseWebIdSigninWindow(signin_window_);
+  view_->CloseSigninPage();
 
   // Do not touch local state here since |this| is now destroyed.
 }
@@ -68,6 +76,8 @@
   auto rp_hostname =
       base::UTF8ToUTF16(rp_web_contents->GetVisibleURL().GetOrigin().host());
 
-  ShowTokenExchangeWebIdPermissionDialog(rp_web_contents, idp_hostname,
-                                         rp_hostname, std::move(callback));
+  // TODO(majidvp): remove rp_web_contents from args
+  DCHECK_EQ(view_->rp_web_contents(), rp_web_contents);
+  view_->ShowTokenExchangePermission(idp_hostname, rp_hostname,
+                                     std::move(callback));
 }
diff --git a/chrome/browser/ui/webid/identity_dialog_controller.h b/chrome/browser/ui/webid/identity_dialog_controller.h
index 5385228f..0965a00 100644
--- a/chrome/browser/ui/webid/identity_dialog_controller.h
+++ b/chrome/browser/ui/webid/identity_dialog_controller.h
@@ -10,7 +10,7 @@
 #include "content/public/browser/web_contents.h"
 
 class GURL;
-class WebIdSigninWindow;
+class WebIdDialog;
 
 using UserApproval = content::IdentityRequestDialogController::UserApproval;
 using InitialApprovalCallback =
@@ -50,8 +50,7 @@
       TokenExchangeApprovalCallback) override;
 
  private:
-  // This object manages its own lifetime
-  WebIdSigninWindow* signin_window_;
+  WebIdDialog* view_{nullptr};
 };
 
 #endif  // CHROME_BROWSER_UI_WEBID_IDENTITY_DIALOG_CONTROLLER_H_
diff --git a/chrome/browser/ui/webid/identity_dialogs.h b/chrome/browser/ui/webid/identity_dialogs.h
deleted file mode 100644
index 4e55271..0000000
--- a/chrome/browser/ui/webid/identity_dialogs.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_WEBID_IDENTITY_DIALOGS_H_
-#define CHROME_BROWSER_UI_WEBID_IDENTITY_DIALOGS_H_
-
-#include <string>
-
-#include "base/callback.h"
-#include "base/strings/string16.h"
-#include "content/public/browser/identity_request_dialog_controller.h"
-#include "url/gurl.h"
-
-class WebIdSigninWindow;
-
-namespace content {
-class WebContents;
-}  // namespace content
-
-// Creates and shows a confirmation dialog for initial permission. The provided
-// callback is called with appropriate status depending on whether user accepted
-// or denied/closed the dialog.
-void ShowInitialWebIdPermissionDialog(
-    content::WebContents* rp_web_contents,
-    const base::string16& idp_hostname,
-    const base::string16& rp_hostname,
-    content::IdentityRequestDialogController::InitialApprovalCallback callback);
-
-// Creates and shows a confirmation dialog for return permission. The provided
-// callback is called with appropriate status depending on whether user accepted
-// or denied/closed the dialog.
-void ShowTokenExchangeWebIdPermissionDialog(
-    content::WebContents* rp_web_contents,
-    const base::string16& idp_hostname,
-    const base::string16& rp_hostname,
-    content::IdentityRequestDialogController::TokenExchangeApprovalCallback
-        callback);
-
-// Creates and shows a window that loads the identity provider sign in page at
-// the given URL. The provided callback is called when IDP has provided an
-// id_token with the id_token a its argument, or when window is closed by user
-// with an empty string as its argument.
-WebIdSigninWindow* ShowWebIdSigninWindow(
-    content::WebContents* rp_web_contents,
-    content::WebContents* idp_web_contents,
-    const GURL& idp_signin_url,
-    content::IdentityRequestDialogController::IdProviderWindowClosedCallback
-        on_done);
-
-void CloseWebIdSigninWindow(WebIdSigninWindow* window);
-
-#endif  // CHROME_BROWSER_UI_WEBID_IDENTITY_DIALOGS_H_
diff --git a/chrome/browser/ui/webid/webid_dialog.cc b/chrome/browser/ui/webid/webid_dialog.cc
new file mode 100644
index 0000000..d4b679d
--- /dev/null
+++ b/chrome/browser/ui/webid/webid_dialog.cc
@@ -0,0 +1,8 @@
+// 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/webid/webid_dialog.h"
+
+WebIdDialog::WebIdDialog(content::WebContents* rp_web_contents)
+    : rp_web_contents_(rp_web_contents) {}
diff --git a/chrome/browser/ui/webid/webid_dialog.h b/chrome/browser/ui/webid/webid_dialog.h
new file mode 100644
index 0000000..e027abc5
--- /dev/null
+++ b/chrome/browser/ui/webid/webid_dialog.h
@@ -0,0 +1,68 @@
+// 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_UI_WEBID_WEBID_DIALOG_H_
+#define CHROME_BROWSER_UI_WEBID_WEBID_DIALOG_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/strings/string16.h"
+#include "content/public/browser/identity_request_dialog_controller.h"
+#include "url/gurl.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+using UserApproval = content::IdentityRequestDialogController::UserApproval;
+using PermissionCallback =
+    content::IdentityRequestDialogController::InitialApprovalCallback;
+using CloseCallback =
+    content::IdentityRequestDialogController::IdProviderWindowClosedCallback;
+
+// The interface for creating and controlling a platform-dependent WebIdDialog.
+class WebIdDialog {
+ public:
+  static WebIdDialog* Create(content::WebContents* rp_web_contents);
+
+  // Creates and shows a confirmation dialog for initial permission. The
+  // provided callback is called with appropriate status depending on whether
+  // user accepted or denied/closed the dialog.
+  virtual void ShowInitialPermission(const base::string16& idp_hostname,
+                                     const base::string16& rp_hostname,
+                                     PermissionCallback) = 0;
+
+  // Creates and shows a confirmation dialog for return permission. The provided
+  // callback is called with appropriate status depending on whether user
+  // accepted or denied/closed the dialog.
+  virtual void ShowTokenExchangePermission(const base::string16& idp_hostname,
+                                           const base::string16& rp_hostname,
+                                           PermissionCallback) = 0;
+
+  // Creates and shows a window that loads the identity provider sign in page at
+  // the given URL. The provided callback is called when IDP has provided an
+  // id_token with the id_token a its argument, or when window is closed by user
+  // with an empty string as its argument.
+  virtual void ShowSigninPage(content::WebContents* idp_web_contents,
+                              const GURL& idp_signin_url,
+                              CloseCallback) = 0;
+
+  // Closes the sign in page. Calling the close callback that was provided
+  // previously.
+  virtual void CloseSigninPage() = 0;
+
+  content::WebContents* rp_web_contents() const { return rp_web_contents_; }
+
+ protected:
+  explicit WebIdDialog(content::WebContents* rp_web_contents);
+  WebIdDialog(const WebIdDialog&) = delete;
+  WebIdDialog& operator=(const WebIdDialog&) = delete;
+  virtual ~WebIdDialog() = default;
+
+ private:
+  content::WebContents* rp_web_contents_;
+};
+
+#endif  // CHROME_BROWSER_UI_WEBID_WEBID_DIALOG_H_
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index e87e311..59f0bb1 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -155,6 +155,10 @@
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_factory.h"
 #include "chrome/browser/ash/login/login_pref_names.h"
+#include "chrome/browser/ash/scanning/scan_service.h"
+#include "chrome/browser/ash/scanning/scan_service_factory.h"
+#include "chrome/browser/ash/scanning/scanning_paths_provider_impl.h"
+#include "chrome/browser/ash/scanning/scanning_util.h"
 #include "chrome/browser/ash/web_applications/chrome_camera_app_ui_delegate.h"
 #include "chrome/browser/ash/web_applications/chrome_help_app_ui_delegate.h"
 #include "chrome/browser/ash/web_applications/chrome_media_app_ui_delegate.h"
@@ -166,10 +170,6 @@
 #include "chrome/browser/chromeos/net/network_health/network_health_service.h"
 #include "chrome/browser/chromeos/printing/print_management/printing_manager.h"
 #include "chrome/browser/chromeos/printing/print_management/printing_manager_factory.h"
-#include "chrome/browser/chromeos/scanning/scan_service.h"
-#include "chrome/browser/chromeos/scanning/scan_service_factory.h"
-#include "chrome/browser/chromeos/scanning/scanning_paths_provider_impl.h"
-#include "chrome/browser/chromeos/scanning/scanning_util.h"
 #include "chrome/browser/chromeos/secure_channel/secure_channel_client_provider.h"
 #include "chrome/browser/feedback/feedback_dialog_utils.h"
 #include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h"
diff --git a/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc b/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc
index 05d596f..6f26f40 100644
--- a/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc
@@ -8,7 +8,6 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/public/ash_interfaces.h"
-#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/event_rewriter_controller.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/public/cpp/tablet_mode.h"
@@ -116,8 +115,6 @@
                base::Value(ash::TabletMode::Get()->InTabletMode()));
   dict->SetKey("isDemoModeEnabled",
                base::Value(DemoSetupController::IsDemoModeAllowed()));
-  dict->SetKey("showTechnologyBadge",
-               base::Value(!ash::features::IsSeparateNetworkIconsEnabled()));
   dict->SetKey("newLayoutEnabled",
                base::Value(features::IsNewOobeLayoutEnabled()));
 }
diff --git a/chrome/browser/ui/webui/chromeos/smb_shares/smb_share_dialog.cc b/chrome/browser/ui/webui/chromeos/smb_shares/smb_share_dialog.cc
index cbc178f..7a050b5 100644
--- a/chrome/browser/ui/webui/chromeos/smb_shares/smb_share_dialog.cc
+++ b/chrome/browser/ui/webui/chromeos/smb_shares/smb_share_dialog.cc
@@ -14,6 +14,7 @@
 #include "chrome/grit/browser_resources.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/strings/grit/components_strings.h"
+#include "components/user_manager/user_manager.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_data_source.h"
 
@@ -86,6 +87,10 @@
       smb_service && smb_service->IsKerberosEnabledViaPolicy();
   source->AddBoolean("isKerberosEnabled", is_kerberos_enabled);
 
+  bool is_guest = user_manager::UserManager::Get()->IsLoggedInAsGuest() ||
+                  user_manager::UserManager::Get()->IsLoggedInAsPublicAccount();
+  source->AddBoolean("isGuest", is_guest);
+
   source->UseStringsJs();
   source->SetDefaultResource(IDR_SMB_SHARES_DIALOG_CONTAINER_HTML);
   source->AddResourcePath("smb_share_dialog.js", IDR_SMB_SHARES_DIALOG_JS);
diff --git a/chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler.cc b/chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler.cc
index d226af3..1bf52c65 100644
--- a/chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler.cc
+++ b/chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler.h"
 
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search/chrome_colors/chrome_colors_factory.h"
 #include "chrome/browser/search/chrome_colors/chrome_colors_service.h"
@@ -13,8 +12,6 @@
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/common/search/generated_colors_info.h"
 #include "chrome/common/themes/autogenerated_theme_util.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_source.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
@@ -39,13 +36,13 @@
   CHECK(profile_);
   CHECK(chrome_colors_service_);
   CHECK(theme_service_);
-  notification_registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-                              content::NotificationService::AllSources());
+  theme_service_->AddObserver(this);
 }
 
 ChromeCustomizeThemesHandler::~ChromeCustomizeThemesHandler() {
   // Revert any unconfirmed theme changes.
   chrome_colors_service_->RevertThemeChangesForTab(web_contents_);
+  theme_service_->RemoveObserver(this);
 }
 
 void ChromeCustomizeThemesHandler::ApplyAutogeneratedTheme(
@@ -104,11 +101,7 @@
   chrome_colors_service_->RevertThemeChanges();
 }
 
-void ChromeCustomizeThemesHandler::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  DCHECK_EQ(type, chrome::NOTIFICATION_BROWSER_THEME_CHANGED);
+void ChromeCustomizeThemesHandler::OnThemeChanged() {
   UpdateTheme();
 }
 
diff --git a/chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler.h b/chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler.h
index 5fe2743..beaa2f7b 100644
--- a/chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler.h
+++ b/chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler.h
@@ -5,8 +5,7 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_CUSTOMIZE_THEMES_CHROME_CUSTOMIZE_THEMES_HANDLER_H_
 #define CHROME_BROWSER_UI_WEBUI_CUSTOMIZE_THEMES_CHROME_CUSTOMIZE_THEMES_HANDLER_H_
 
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -32,7 +31,7 @@
 // receiver about all theme updates in the current profile.
 class ChromeCustomizeThemesHandler
     : public customize_themes::mojom::CustomizeThemesHandler,
-      public content::NotificationObserver {
+      public ThemeServiceObserver {
  public:
   explicit ChromeCustomizeThemesHandler(
       mojo::PendingRemote<customize_themes::mojom::CustomizeThemesClient>
@@ -52,10 +51,8 @@
   void ConfirmThemeChanges() override;
   void RevertThemeChanges() override;
 
-  // content::NotificationObserver:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  // ThemeServiceObserver:
+  void OnThemeChanged() override;
 
  private:
   void UpdateTheme();
@@ -67,8 +64,6 @@
   Profile* const profile_;
   chrome_colors::ChromeColorsService* const chrome_colors_service_;
   ThemeService* const theme_service_;
-
-  content::NotificationRegistrar notification_registrar_;
 };
 
 #endif  // CHROME_BROWSER_UI_WEBUI_CUSTOMIZE_THEMES_CHROME_CUSTOMIZE_THEMES_HANDLER_H_
diff --git a/chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler_unittest.cc b/chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler_unittest.cc
index 15cbf20c..2c7de975 100644
--- a/chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler_unittest.cc
+++ b/chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler_unittest.cc
@@ -15,8 +15,8 @@
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/values.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/test_extension_environment.h"
+#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/common/chrome_paths.h"
@@ -235,13 +235,11 @@
       base::JSONReader::Read(config_contents);
   ASSERT_TRUE(manifest.has_value());
 
-  content::WindowedNotificationObserver theme_change_observer(
-      chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-      content::Source<ThemeService>(theme_service()));
+  test::ThemeServiceChangedWaiter waiter(theme_service());
   EXPECT_CALL(*mock_client(), SetTheme(MatchesThirdPartyTheme(
                                   kThemeExtensionId, kThemeExtensionName)));
   env()->MakeExtension(manifest.value(), kThemeExtensionId);
-  theme_change_observer.Wait();
+  waiter.WaitForThemeChanged();
 }
 
 TEST_F(ChromeCustomizeThemesHandlerTest, RevertThemeChanges) {
diff --git a/chrome/browser/ui/webui/memories/memories_ui.cc b/chrome/browser/ui/webui/memories/memories_ui.cc
index 2b355e73..91857ef 100644
--- a/chrome/browser/ui/webui/memories/memories_ui.cc
+++ b/chrome/browser/ui/webui/memories/memories_ui.cc
@@ -5,12 +5,16 @@
 #include "chrome/browser/ui/webui/memories/memories_ui.h"
 
 #include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/favicon_source.h"
 #include "chrome/browser/ui/webui/memories/memories_handler.h"
+#include "chrome/browser/ui/webui/sanitized_image_source.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/memories_resources.h"
 #include "chrome/grit/memories_resources_map.h"
+#include "components/favicon_base/favicon_url_parser.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui_controller.h"
@@ -19,7 +23,7 @@
 
 namespace {
 
-content::WebUIDataSource* CreateAndSetupWebUIDataSource() {
+content::WebUIDataSource* CreateAndSetupWebUIDataSource(Profile* profile) {
   content::WebUIDataSource* source =
       content::WebUIDataSource::Create(chrome::kChromeUIMemoriesHost);
 
@@ -28,6 +32,10 @@
   };
   source->AddLocalizedStrings(kStrings);
 
+  // TODO(crbug.com/1173908): Replace these with localized strings.
+  source->AddString("memoryTitleDescription",
+                    base::UTF8ToUTF16("Based on previous web activity"));
+
   webui::SetupWebUIDataSource(
       source, base::make_span(kMemoriesResources, kMemoriesResourcesSize),
       IDR_MEMORIES_MEMORIES_HTML);
@@ -44,7 +52,7 @@
   DCHECK(profile_);
   DCHECK(web_contents_);
 
-  auto* source = CreateAndSetupWebUIDataSource();
+  auto* source = CreateAndSetupWebUIDataSource(profile_);
   content::WebUIDataSource::Add(profile_, source);
 }
 
diff --git a/chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_handler.cc b/chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_handler.cc
index 8cbcb9b..fd7415a 100644
--- a/chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_handler.cc
+++ b/chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_handler.cc
@@ -7,7 +7,6 @@
 #include "base/feature_list.h"
 #include "base/strings/strcat.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/ntp_tiles/chrome_most_visited_sites_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
@@ -24,7 +23,6 @@
 #include "components/prefs/pref_service.h"
 #include "components/search/ntp_features.h"
 #include "components/search_engines/template_url_service.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/gfx/color_utils.h"
 
@@ -47,12 +45,12 @@
       this, ntp_tiles::kMaxNumMostVisited);
   most_visited_sites_->EnableCustomLinks(false);
   // Listen for theme installation.
-  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-                 content::Source<ThemeService>(
-                     ThemeServiceFactory::GetForProfile(profile_)));
+  ThemeServiceFactory::GetForProfile(profile_)->AddObserver(this);
 }
 
-NewTabPageThirdPartyHandler::~NewTabPageThirdPartyHandler() = default;
+NewTabPageThirdPartyHandler::~NewTabPageThirdPartyHandler() {
+  ThemeServiceFactory::GetForProfile(profile_)->RemoveObserver(this);
+}
 
 void NewTabPageThirdPartyHandler::DeleteMostVisitedTile(const GURL& url) {
   most_visited_sites_->AddOrRemoveBlockedUrl(url, true);
@@ -138,17 +136,8 @@
       false));
 }
 
-void NewTabPageThirdPartyHandler::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  switch (type) {
-    case chrome::NOTIFICATION_BROWSER_THEME_CHANGED:
-      NotifyAboutTheme();
-      break;
-    default:
-      NOTREACHED() << "Unexpected notification type in InstantService.";
-  }
+void NewTabPageThirdPartyHandler::OnThemeChanged() {
+  NotifyAboutTheme();
 }
 
 void NewTabPageThirdPartyHandler::OnURLsAvailable(
diff --git a/chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_handler.h b/chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_handler.h
index 30f9f66..08ffd6f 100644
--- a/chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_handler.h
+++ b/chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_handler.h
@@ -10,14 +10,13 @@
 #include <vector>
 
 #include "base/time/time.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "chrome/browser/ui/search/ntp_user_data_logger.h"
 #include "chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party.mojom.h"
 #include "chrome/common/search/ntp_logging_events.h"
 #include "components/ntp_tiles/most_visited_sites.h"
 #include "components/ntp_tiles/ntp_tile.h"
 #include "components/ntp_tiles/section_type.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -37,7 +36,7 @@
 class NewTabPageThirdPartyHandler
     : public new_tab_page_third_party::mojom::PageHandler,
       public ntp_tiles::MostVisitedSites::Observer,
-      public content::NotificationObserver,
+      public ThemeServiceObserver,
       public ui::NativeThemeObserver {
  public:
   NewTabPageThirdPartyHandler(
@@ -68,10 +67,8 @@
   void UpdateTheme() override;
 
  private:
-  // content::NotificationObserver:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  // ThemeServiceObserver:
+  void OnThemeChanged() override;
 
   // ntp_tiles::MostVisitedSites::Observer implementation.
   void OnURLsAvailable(
@@ -92,7 +89,6 @@
   NTPUserDataLogger logger_;
   base::Time ntp_navigation_start_time_;
   GURL last_blocklisted_;
-  content::NotificationRegistrar registrar_;
 
   // These are located at the end of the list of member variables to ensure the
   // WebUI page is disconnected before other members are destroyed.
diff --git a/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc b/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc
index 0a58f2b..37724b5 100644
--- a/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc
+++ b/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc
@@ -18,7 +18,6 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/signin_util.h"
@@ -48,7 +47,6 @@
 #include "components/reading_list/features/reading_list_switches.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_process_host.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -156,9 +154,7 @@
 
 NTPResourceCache::NTPResourceCache(Profile* profile)
     : profile_(profile), is_swipe_tracking_from_scroll_events_enabled_(false) {
-  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-                 content::Source<ThemeService>(
-                     ThemeServiceFactory::GetForProfile(profile)));
+  ThemeServiceFactory::GetForProfile(profile_)->AddObserver(this);
 
   base::RepeatingClosure callback = base::BindRepeating(
       &NTPResourceCache::OnPreferenceChanged, base::Unretained(this));
@@ -265,15 +261,14 @@
   return new_tab_css_.get();
 }
 
-void NTPResourceCache::Observe(int type,
-                               const content::NotificationSource& source,
-                               const content::NotificationDetails& details) {
-  DCHECK_EQ(chrome::NOTIFICATION_BROWSER_THEME_CHANGED, type);
-
-  // Invalidate the cache.
+void NTPResourceCache::OnThemeChanged() {
   Invalidate();
 }
 
+void NTPResourceCache::Shutdown() {
+  ThemeServiceFactory::GetForProfile(profile_)->RemoveObserver(this);
+}
+
 void NTPResourceCache::OnNativeThemeUpdated(ui::NativeTheme* updated_theme) {
   // TODO(crbug/1056916): Remove the global accessor to NativeTheme.
   DCHECK_EQ(updated_theme, ui::NativeTheme::GetInstanceForNativeUi());
diff --git a/chrome/browser/ui/webui/ntp/ntp_resource_cache.h b/chrome/browser/ui/webui/ntp/ntp_resource_cache.h
index 3c765fd..93a001f2 100644
--- a/chrome/browser/ui/webui/ntp/ntp_resource_cache.h
+++ b/chrome/browser/ui/webui/ntp/ntp_resource_cache.h
@@ -13,10 +13,9 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/scoped_observation.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/prefs/pref_change_registrar.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/native_theme/native_theme.h"
 #include "ui/native_theme/native_theme_observer.h"
@@ -52,7 +51,7 @@
 // regenerate them all the time.
 // Note: This is only used for incognito and guest mode NTPs (NewTabUI), as well
 // as for (non-incognito) app launcher pages (AppLauncherPageUI).
-class NTPResourceCache : public content::NotificationObserver,
+class NTPResourceCache : public ThemeServiceObserver,
                          public KeyedService,
                          public ui::NativeThemeObserver {
  public:
@@ -73,10 +72,8 @@
       WindowType win_type,
       const content::WebContents::Getter wc_getter);
 
-  // content::NotificationObserver:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  // ThemeServiceObserver:
+  void OnThemeChanged() override;
 
   static WindowType GetWindowType(
       Profile* profile, content::RenderProcessHost* render_host);
@@ -104,6 +101,9 @@
     int warnings_ids;
   };
 
+  // KeyedService:
+  void Shutdown() override;
+
   // ui::NativeThemeObserver:
   void OnNativeThemeUpdated(ui::NativeTheme* updated_theme) override;
 
@@ -145,7 +145,6 @@
   scoped_refptr<base::RefCountedMemory> new_tab_incognito_html_;
   scoped_refptr<base::RefCountedMemory> new_tab_incognito_css_;
   scoped_refptr<base::RefCountedMemory> new_tab_non_primary_otr_html_;
-  content::NotificationRegistrar registrar_;
   PrefChangeRegistrar profile_pref_change_registrar_;
   PrefChangeRegistrar local_state_pref_change_registrar_;
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/main_section.cc b/chrome/browser/ui/webui/settings/chromeos/main_section.cc
index 023895cf..981f56e 100644
--- a/chrome/browser/ui/webui/settings/chromeos/main_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/main_section.cc
@@ -254,6 +254,10 @@
   plural_string_handler->AddLocalizedString(
       "nearbyShareContactVisibilityNumUnreachable",
       IDS_NEARBY_CONTACT_VISIBILITY_NUM_UNREACHABLE);
+
+  plural_string_handler->AddLocalizedString(
+      "lockScreenNumberFingerprints",
+      IDS_SETTINGS_PEOPLE_LOCK_SCREEN_NUM_FINGERPRINTS);
   return plural_string_handler;
 }
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/people_section.cc b/chrome/browser/ui/webui/settings/chromeos/people_section.cc
index 7821284..ebb104ce9 100644
--- a/chrome/browser/ui/webui/settings/chromeos/people_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/people_section.cc
@@ -444,8 +444,6 @@
        IDS_SETTINGS_PEOPLE_LOCK_SCREEN_CHANGE_PIN_BUTTON},
       {"lockScreenEditFingerprintsDescription",
        IDS_SETTINGS_PEOPLE_LOCK_SCREEN_EDIT_FINGERPRINTS_DESCRIPTION},
-      {"lockScreenNumberFingerprints",
-       IDS_SETTINGS_PEOPLE_LOCK_SCREEN_NUM_FINGERPRINTS},
       {"lockScreenNone", IDS_SETTINGS_PEOPLE_LOCK_SCREEN_NONE},
       {"lockScreenFingerprintNewName",
        IDS_SETTINGS_PEOPLE_LOCK_SCREEN_NEW_FINGERPRINT_DEFAULT_NAME},
diff --git a/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc b/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc
index be5ac2b..538ea31 100644
--- a/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc
@@ -10,6 +10,7 @@
 #include "base/no_destructor.h"
 #include "build/branding_buildflags.h"
 #include "chrome/browser/ash/login/quick_unlock/quick_unlock_utils.h"
+#include "chrome/browser/ui/webui/settings/chromeos/os_settings_features_util.h"
 #include "chrome/browser/ui/webui/settings/chromeos/peripheral_data_access_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.h"
@@ -44,7 +45,8 @@
          {.section = mojom::Section::kPrivacyAndSecurity}},
     });
 
-    if (chromeos::features::IsAccountManagementFlowsV2Enabled()) {
+    if (chromeos::features::IsAccountManagementFlowsV2Enabled() &&
+        !features::IsGuestModeActive()) {
       all_tags.insert(
           all_tags.end(),
           {{IDS_OS_SETTINGS_TAG_GUEST_BROWSING,
@@ -185,7 +187,7 @@
 
   // Fingerprint search tags are added if necessary. Remove fingerprint search
   // tags update dynamically during a user session.
-  if (AreFingerprintSettingsAllowed() &&
+  if (!features::IsGuestModeActive() && AreFingerprintSettingsAllowed() &&
       chromeos::features::IsAccountManagementFlowsV2Enabled()) {
     updater.AddSearchTags(GetFingerprintSearchConcepts());
 
diff --git a/chrome/browser/ui/webui/tab_search/tab_search.mojom b/chrome/browser/ui/webui/tab_search/tab_search.mojom
index 245812d..8096a31 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search.mojom
+++ b/chrome/browser/ui/webui/tab_search/tab_search.mojom
@@ -52,8 +52,12 @@
   // Whether the tab strip should show the icon.
   bool show_icon;
 
-  // Time ticks when the tab is last active.
+  // Time ticks when the tab is last active. This value is used to order open
+  // tabs by recency.
   mojo_base.mojom.TimeTicks last_active_time_ticks;
+
+  // String representing the elapsed time since the tab was last active.
+  string last_active_elapsed_text;
 };
 
 // Information about a recently closed tab.
@@ -65,8 +69,12 @@
   // TODO(crbug.com/1099917): Change this and elsewhere to use url.mojom.Url.
   string url;
 
-  // Time when the tab is last active.
+  // Elapsed time since the tab was last closed. This value is used to order
+  // closed tabs by recency.
   mojo_base.mojom.Time last_active_time_ticks;
+
+  // String representing the elapsed time since the tab was closed.
+  string last_active_elapsed_text;
 };
 
 // Collection of tab groups.
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
index c0110ff..fe9ac61 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
+++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
@@ -27,6 +27,7 @@
 #include "chrome/browser/ui/tabs/tab_renderer_data.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/webui/util/image_util.h"
+#include "ui/base/l10n/time_format.h"
 
 namespace {
 constexpr base::TimeDelta kTabsChangeDelay =
@@ -37,7 +38,15 @@
 #else
 constexpr char kFeedbackCategoryTag[] = "FromTabSearchBrowser";
 #endif
+
+std::string GetLastActiveElapsedText(
+    const base::TimeTicks& last_active_time_ticks) {
+  const base::TimeDelta elapsed =
+      base::TimeTicks::Now() - last_active_time_ticks;
+  return base::UTF16ToUTF8(ui::TimeFormat::Simple(
+      ui::TimeFormat::FORMAT_ELAPSED, ui::TimeFormat::LENGTH_SHORT, elapsed));
 }
+}  // namespace
 
 TabSearchPageHandler::TabSearchPageHandler(
     mojo::PendingReceiver<tab_search::mojom::PageHandler> receiver,
@@ -213,7 +222,11 @@
   }
 
   tab_data->show_icon = tab_renderer_data.show_icon;
-  tab_data->last_active_time_ticks = contents->GetLastActiveTime();
+
+  const base::TimeTicks last_active_time_ticks = contents->GetLastActiveTime();
+  tab_data->last_active_time_ticks = last_active_time_ticks;
+  tab_data->last_active_elapsed_text =
+      GetLastActiveElapsedText(last_active_time_ticks);
 
   return tab_data;
 }
diff --git a/chrome/browser/ui/webui/theme_handler.cc b/chrome/browser/ui/webui/theme_handler.cc
index aca8e65..eca1912 100644
--- a/chrome/browser/ui/webui/theme_handler.cc
+++ b/chrome/browser/ui/webui/theme_handler.cc
@@ -8,21 +8,22 @@
 
 #include "base/bind.h"
 #include "base/values.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/webui/theme_source.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/grit/theme_resources.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/browser/web_ui.h"
 
 ///////////////////////////////////////////////////////////////////////////////
 // ThemeHandler
 
 ThemeHandler::ThemeHandler() = default;
-ThemeHandler::~ThemeHandler() = default;
+
+ThemeHandler::~ThemeHandler() {
+  ThemeServiceFactory::GetForProfile(GetProfile())->RemoveObserver(this);
+}
 
 void ThemeHandler::RegisterMessages() {
   // These are not actual message registrations, but can't be done in the
@@ -37,9 +38,8 @@
 
 void ThemeHandler::OnJavascriptAllowed() {
   // Listen for theme installation.
-  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
-                 content::Source<ThemeService>(
-                     ThemeServiceFactory::GetForProfile(GetProfile())));
+  ThemeServiceFactory::GetForProfile(GetProfile())->AddObserver(this);
+
   // Or native theme change.
   if (web_ui()) {
     theme_observation_.Observe(
@@ -48,14 +48,11 @@
 }
 
 void ThemeHandler::OnJavascriptDisallowed() {
-  registrar_.RemoveAll();
+  ThemeServiceFactory::GetForProfile(GetProfile())->RemoveObserver(this);
   theme_observation_.Reset();
 }
 
-void ThemeHandler::Observe(int type,
-                           const content::NotificationSource& source,
-                           const content::NotificationDetails& details) {
-  DCHECK_EQ(chrome::NOTIFICATION_BROWSER_THEME_CHANGED, type);
+void ThemeHandler::OnThemeChanged() {
   SendThemeChanged();
 }
 
diff --git a/chrome/browser/ui/webui/theme_handler.h b/chrome/browser/ui/webui/theme_handler.h
index f7c0c35..af2a7ae6 100644
--- a/chrome/browser/ui/webui/theme_handler.h
+++ b/chrome/browser/ui/webui/theme_handler.h
@@ -7,8 +7,7 @@
 
 #include "base/macros.h"
 #include "base/scoped_observation.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "content/public/browser/web_ui_message_handler.h"
 #include "ui/native_theme/native_theme.h"
 #include "ui/native_theme/native_theme_observer.h"
@@ -21,7 +20,7 @@
 
 // A class to keep the ThemeSource up to date when theme changes.
 class ThemeHandler : public content::WebUIMessageHandler,
-                     public content::NotificationObserver,
+                     public ThemeServiceObserver,
                      public ui::NativeThemeObserver {
  public:
   ThemeHandler();
@@ -36,10 +35,8 @@
   // Re/set the CSS caches.
   void InitializeCSSCaches();
 
-  // content::NotificationObserver implementation.
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  // ThemeServiceObserver implementation.
+  void OnThemeChanged() override;
 
   // ui::NativeThemeObserver:
   void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override;
@@ -52,8 +49,6 @@
 
   Profile* GetProfile() const;
 
-  content::NotificationRegistrar registrar_;
-
   base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver>
       theme_observation_{this};
 
diff --git a/chrome/browser/update_client/chrome_update_query_params_delegate.cc b/chrome/browser/update_client/chrome_update_query_params_delegate.cc
index db647b2c..7a5bf13 100644
--- a/chrome/browser/update_client/chrome_update_query_params_delegate.cc
+++ b/chrome/browser/update_client/chrome_update_query_params_delegate.cc
@@ -30,10 +30,10 @@
 }
 
 std::string ChromeUpdateQueryParamsDelegate::GetExtraParams() {
-  return base::StringPrintf("&prodchannel=%s&prodversion=%s&lang=%s",
-                            chrome::GetChannelName().c_str(),
-                            version_info::GetVersionNumber().c_str(),
-                            GetLang());
+  return base::StringPrintf(
+      "&prodchannel=%s&prodversion=%s&lang=%s",
+      chrome::GetChannelName(chrome::WithExtendedStable(true)).c_str(),
+      version_info::GetVersionNumber().c_str(), GetLang());
 }
 
 // static
diff --git a/chrome/browser/update_client/chrome_update_query_params_delegate_unittest.cc b/chrome/browser/update_client/chrome_update_query_params_delegate_unittest.cc
index f61fc33..fe224c82 100644
--- a/chrome/browser/update_client/chrome_update_query_params_delegate_unittest.cc
+++ b/chrome/browser/update_client/chrome_update_query_params_delegate_unittest.cc
@@ -39,8 +39,11 @@
       params, StringPrintf(
                   "prod=%s",
                   update_client::UpdateQueryParams::GetProdIdString(prod_id))));
-  EXPECT_TRUE(Contains(params, StringPrintf("prodchannel=%s",
-                                            chrome::GetChannelName().c_str())));
+  EXPECT_TRUE(Contains(
+      params,
+      StringPrintf(
+          "prodchannel=%s",
+          chrome::GetChannelName(chrome::WithExtendedStable(true)).c_str())));
   EXPECT_TRUE(
       Contains(params, StringPrintf("prodversion=%s",
                                     version_info::GetVersionNumber().c_str())));
diff --git a/chrome/browser/web_applications/components/web_app_constants.cc b/chrome/browser/web_applications/components/web_app_constants.cc
index 7248301..3c18b856 100644
--- a/chrome/browser/web_applications/components/web_app_constants.cc
+++ b/chrome/browser/web_applications/components/web_app_constants.cc
@@ -42,6 +42,21 @@
 
 static_assert(Source::kMinValue == 0, "Source enum should be zero based");
 
+std::ostream& operator<<(std::ostream& os, Source::Type type) {
+  switch (type) {
+    case Source::Type::kSystem:
+      return os << "System";
+    case Source::Type::kPolicy:
+      return os << "Policy";
+    case Source::Type::kWebAppStore:
+      return os << "WebAppStore";
+    case Source::Type::kSync:
+      return os << "Sync";
+    case Source::Type::kDefault:
+      return os << "Default";
+  }
+}
+
 static_assert(OsHookType::kShortcuts == 0,
               "OsHookType enum should be zero based");
 
diff --git a/chrome/browser/web_applications/components/web_app_constants.h b/chrome/browser/web_applications/components/web_app_constants.h
index 3b3694f..fc8d0212 100644
--- a/chrome/browser/web_applications/components/web_app_constants.h
+++ b/chrome/browser/web_applications/components/web_app_constants.h
@@ -34,6 +34,8 @@
 };
 }  // namespace Source
 
+std::ostream& operator<<(std::ostream& os, Source::Type type);
+
 // Type of OS hook.
 //
 // This enum should be zero based. It is not strongly typed enum class to
diff --git a/chrome/browser/web_applications/web_app.cc b/chrome/browser/web_applications/web_app.cc
index f4be6a3..ac19aa0 100644
--- a/chrome/browser/web_applications/web_app.cc
+++ b/chrome/browser/web_applications/web_app.cc
@@ -327,9 +327,16 @@
       << "  user_page_ordinal: " << app.user_page_ordinal_.ToDebugString()
       << std::endl
       << "  user_launch_ordinal: " << app.user_launch_ordinal_.ToDebugString()
-      << std::endl
-      << "  sources: " << app.sources_.to_string() << std::endl
-      << "  is_locally_installed: " << app.is_locally_installed_ << std::endl
+      << std::endl;
+
+  out << "  sources: ";
+  for (int i = Source::Type::kMinValue; i <= Source::Type::kMaxValue; ++i) {
+    if (app.sources_[i])
+      out << static_cast<Source::Type>(i) << " ";
+  }
+  out << std::endl;
+
+  out << "  is_locally_installed: " << app.is_locally_installed_ << std::endl
       << "  is_in_sync_install: " << app.is_in_sync_install_ << std::endl
       << "  sync_fallback_data: " << std::endl
       << app.sync_fallback_data_  // Outputs a std::endl.
diff --git a/chrome/browser/web_applications/web_app_install_manager.cc b/chrome/browser/web_applications/web_app_install_manager.cc
index b29aa0e5..b6ff85b5 100644
--- a/chrome/browser/web_applications/web_app_install_manager.cc
+++ b/chrome/browser/web_applications/web_app_install_manager.cc
@@ -391,6 +391,9 @@
   auto task = std::make_unique<WebAppInstallTask>(
       profile(), os_integration_manager(), finalizer(),
       data_retriever_factory_.Run(), registrar());
+  // Set the expect app id for fallback install too. This can avoid duplicate
+  // installs.
+  task->ExpectAppId(sync_app_id);
 
   InstallFinalizer::FinalizeOptions finalize_options;
   finalize_options.install_source = webapps::WebappInstallSource::SYNC;
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 13e1236..24f76a2 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1615355646-efebb566222ff017079b26dfac76f2e663340cae.profdata
+chrome-linux-master-1615399152-ed68f34ecd63191647774b06c46e4357c6dae108.profdata
diff --git a/chrome/common/channel_info_mac.mm b/chrome/common/channel_info_mac.mm
index e3f42f99..013bdb9e 100644
--- a/chrome/common/channel_info_mac.mm
+++ b/chrome/common/channel_info_mac.mm
@@ -17,48 +17,67 @@
 
 namespace {
 
-std::string ChannelName() {
+struct ChannelState {
+  std::string name;
+  bool is_extended_stable;
+};
+
+// For a branded build, returns its ChannelState: `name` of "" (for stable or
+// extended), "beta", "dev", "canary", or "unknown" (in the case where the
+// channel could not be determined or is otherwise inapplicable), and an
+// `is_extended_stable` with a value corresponding to whether it is an extended
+// stable build or not.
+//
+// For an unbranded build, always returns a ChannelState with `name` of "" and
+// `is_extended_stable` of false.
+const ChannelState& GetChannelState() {
+  static const base::NoDestructor<ChannelState> channel([] {
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  static const base::NoDestructor<std::string> channel([] {
     // Use the main Chrome application bundle and not the framework bundle.
     // Keystone keys don't live in the framework.
     NSBundle* bundle = base::mac::OuterBundle();
-    NSString* channel = [bundle objectForInfoDictionaryKey:@"KSChannelID"];
 
-    // Only ever return "", "unknown", "beta", "dev", or "canary" in a branded
-    // build.
-    // KSProductID is not set (for stable) or "beta", "dev" or "canary" for
-    // the intel-only build.
-    // KSProductID is "arm64" (for stable) or "arm64-beta", "arm64-dev" or
-    // "arm64-canary" for the arm-only build.
-    // KSProductID is "universal" (for stable) or "universal-beta",
-    // "universal-dev" or "universal-canary" for the arm+intel universal binary.
     if (![bundle objectForInfoDictionaryKey:@"KSProductID"]) {
-      // This build is not Keystone-enabled, it can't have a channel.
-      channel = @"unknown";
-    } else if (!channel || [channel isEqual:@"arm64"] ||
-               [channel isEqual:@"universal"]) {
-      // For the intel stable channel, KSChannelID is not set.
-      channel = @"";
-    } else {
-      if ([channel hasPrefix:@"arm64-"])
-        channel = [channel substringFromIndex:[@"arm64-" length]];
-      else if ([channel hasPrefix:@"universal-"])
-        channel = [channel substringFromIndex:[@"universal-" length]];
-      if ([channel isEqual:@"beta"] || [channel isEqual:@"dev"] ||
-          [channel isEqual:@"canary"]) {
-        // Do nothing.
-      } else {
-        channel = @"unknown";
-      }
+      // This build is not Keystone-enabled; it can't have a channel.
+      return ChannelState{"unknown", false};
     }
 
-    return base::SysNSStringToUTF8(channel);
+    // KSChannelID values:
+    //
+    //                     Intel       Arm              Universal
+    //                   ┌───────────┬────────────────┬────────────────────┐
+    //  Stable           │ (not set) │ arm64          │ universal          │
+    //  Extended Stable  │ extended  │ arm64-extended │ universal-extended │
+    //  Beta             │ beta      │ arm64-beta     │ universal-beta     │
+    //  Dev              │ dev       │ arm64-dev      │ universal-dev      │
+    //  Canary           │ canary    │ arm64-canary   │ universal-canary   │
+    //                   └───────────┴────────────────┴────────────────────┘
+    NSString* channel = [bundle objectForInfoDictionaryKey:@"KSChannelID"];
+
+    if (!channel || [channel isEqual:@"arm64"] ||
+        [channel isEqual:@"universal"]) {
+      return ChannelState{"", false};  // "" means stable channel.
+    }
+
+    if ([channel hasPrefix:@"arm64-"])
+      channel = [channel substringFromIndex:[@"arm64-" length]];
+    else if ([channel hasPrefix:@"universal-"])
+      channel = [channel substringFromIndex:[@"universal-" length]];
+
+    if ([channel isEqual:@"extended"])
+      return ChannelState{"", true};  // "" means stable channel.
+
+    if ([channel isEqual:@"beta"] || [channel isEqual:@"dev"] ||
+        [channel isEqual:@"canary"]) {
+      return ChannelState{base::SysNSStringToUTF8(channel), false};
+    }
+
+    return ChannelState{"unknown", false};
+#else
+    return ChannelState{"", false};
+#endif
   }());
   return *channel;
-#else
-  return std::string();
-#endif
 }
 
 bool SideBySideCapable() {
@@ -73,7 +92,7 @@
       return true;
     }
 
-    if (GetChannelName().empty()) {
+    if (GetChannelState().name.empty()) {
       // For the stable channel, GetChannelName() returns the empty string.
       // Stable Chromes are what side-by-side capable Chromes are running
       // side-by-side *to* and by definition are side-by-side capable.
@@ -94,13 +113,17 @@
 }  // namespace
 
 void CacheChannelInfo() {
-  ignore_result(ChannelName());
+  ignore_result(GetChannelState());
   ignore_result(SideBySideCapable());
 }
 
 std::string GetChannelName(WithExtendedStable with_extended_stable) {
-  // TODO(https://crbug.com/1185621): Detect extended stable.
-  return ChannelName();
+  const auto& channel = GetChannelState();
+
+  if (channel.is_extended_stable && with_extended_stable.value())
+    return "extended";
+
+  return channel.name;
 }
 
 version_info::Channel GetChannelByName(const std::string& channel) {
@@ -122,12 +145,11 @@
 }
 
 version_info::Channel GetChannel() {
-  return GetChannelByName(ChannelName());
+  return GetChannelByName(GetChannelState().name);
 }
 
 bool IsExtendedStableChannel() {
-  // TODO(https://crbug.com/1185621): Detect extended stable.
-  return false;
+  return GetChannelState().is_extended_stable;
 }
 
 }  // namespace chrome
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index aca5a5b8..3f0bf2b 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -979,4 +979,8 @@
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+// Enables link to text to be generated in advance.
+const base::Feature kPreemtiveLinkToTextGeneration{
+    "PreemtiveLinkToTextGeneration", base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index ca4a1a9..03cc539 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -114,6 +114,9 @@
 #endif
 
 COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kPreemtiveLinkToTextGeneration;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kPrivacySandboxSettings;
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::FeatureParam<std::string> kPrivacySandboxSettingsURL;
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json
index eb0b0534..19475ba 100644
--- a/chrome/common/extensions/api/_api_features.json
+++ b/chrome/common/extensions/api/_api_features.json
@@ -347,15 +347,30 @@
     "channel": "stable",
     "contexts": ["blessed_extension", "content_script"]
   },
-  "downloads": {
-    "dependencies": ["permission:downloads"],
-    "contexts": ["blessed_extension"]
-  },
-  "downloadsInternal": {
-    "internal": true,
-    "channel": "stable",
-    "contexts": ["blessed_extension"]
-  },
+  "downloads": [
+    {
+      "dependencies": ["permission:downloads"],
+      "contexts": ["blessed_extension"]
+    },
+    {
+      "channel": "stable",
+      "contexts": ["webui"],
+      "matches": ["chrome://download-shelf.top-chrome/*"]
+    }
+  ],
+  "downloadsInternal": [
+    {
+      "internal": true,
+      "channel": "stable",
+      "contexts": ["blessed_extension"]
+    },
+    {
+      "internal": true,
+      "channel": "stable",
+      "contexts": ["webui"],
+      "matches": ["chrome://download-shelf.top-chrome/*"]
+    }
+  ],
   "echoPrivate": {
     "dependencies": ["permission:echoPrivate"],
     "contexts": ["blessed_extension"]
diff --git a/chrome/common/mac/app_shim.mojom b/chrome/common/mac/app_shim.mojom
index 2506712..3d2a3e6 100644
--- a/chrome/common/mac/app_shim.mojom
+++ b/chrome/common/mac/app_shim.mojom
@@ -43,6 +43,18 @@
   kCritical,
 };
 
+// Enum describing if the application was launched automatically as a
+// 'Login Item' or via Resume.
+enum AppShimLoginItemRestoreState {
+  // The appilcation as not launched during OS login.
+  kNone,
+  // The application was launched during OS login.
+  kWindowed,
+  // The application was launched during OS login, with the
+  // 'hide on startup' flag.
+  kHidden,
+};
+
 // An entry in the profiles NSMenu.
 struct ProfileMenuItem {
   // The name to display.
@@ -121,6 +133,9 @@
 
   // The files that were dragged on to this app when launching it.
   array<mojo_base.mojom.FilePath> files;
+
+  // Indicates if the app launched during OS login.
+  AppShimLoginItemRestoreState login_item_restore_state;
 };
 
 // The initial interface provided by the browser process. Used to bootstrap to
diff --git a/chrome/installer/mac/BUILD.gn b/chrome/installer/mac/BUILD.gn
index d010722..9cc2f6e7 100644
--- a/chrome/installer/mac/BUILD.gn
+++ b/chrome/installer/mac/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/features.gni")
+import("//build/config/mac/base_rules.gni")
 import("//build/util/branding.gni")
 import("//build/util/version.gni")
 import("//chrome/process_version_rc_template.gni")
diff --git a/chrome/services/cups_proxy/socket_manager.cc b/chrome/services/cups_proxy/socket_manager.cc
index 608c820..3d800425 100644
--- a/chrome/services/cups_proxy/socket_manager.cc
+++ b/chrome/services/cups_proxy/socket_manager.cc
@@ -263,7 +263,7 @@
   base::OnceClosure cb;
   if (success) {
     cb = base::BindOnce(std::move(in_flight_->cb),
-                        base::Passed(&in_flight_->response));
+                        std::move(in_flight_->response));
   } else {
     cb = base::BindOnce(std::move(in_flight_->cb), nullptr);
   }
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index a6d3f1b..e16f09fb 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -203,6 +203,8 @@
     sources += [
       "../browser/banners/test_app_banner_manager_desktop.cc",
       "../browser/banners/test_app_banner_manager_desktop.h",
+      "../browser/themes/test/theme_service_changed_waiter.cc",
+      "../browser/themes/test/theme_service_changed_waiter.h",
     ]
   }
 
@@ -3641,8 +3643,6 @@
     "../browser/optimization_guide/prediction/prediction_model_download_manager_unittest.cc",
     "../browser/page_load_metrics/metrics_web_contents_observer_unittest.cc",
     "../browser/page_load_metrics/observers/aborts_page_load_metrics_observer_unittest.cc",
-    "../browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc",
-    "../browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker_unittest.cc",
     "../browser/page_load_metrics/observers/core/amp_page_load_metrics_observer_unittest.cc",
     "../browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc",
     "../browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.cc",
@@ -4254,6 +4254,7 @@
     "//components/reporting/client:test_support",
     "//components/reporting/storage:storage_uploader_interface",
     "//components/reporting/storage:test_support",
+    "//components/reporting/util:test_callbacks_support",
     "//components/resources",
     "//components/safe_browsing:buildflags",
     "//components/safe_browsing/content/password_protection:mock_password_protection",
@@ -6192,6 +6193,7 @@
       "../browser/ui/views/user_education/feature_promo_bubble_view_unittest.cc",
       "../browser/ui/views/user_education/feature_promo_controller_views_unittest.cc",
       "../browser/ui/views/user_education/tip_marquee_view_unittest.cc",
+      "../browser/ui/views/webid/webid_dialog_views_unittest.cc",
       "../browser/ui/views/window_name_prompt_unittest.cc",
     ]
     if (is_linux || is_chromeos_lacros) {
@@ -7159,6 +7161,7 @@
       "//components/sync:test_support_model",
       "//components/sync:test_support_nigori",
       "//components/sync/test/fake_server",
+      "//components/sync/trusted_vault:test_support",
       "//content/public/browser",
       "//content/test:test_support",
       "//net",
diff --git a/chrome/test/data/extensions/api_test/enterprise_platform_keys/background.js b/chrome/test/data/extensions/api_test/enterprise_platform_keys/background.js
index 0c7d452..1b1c70ea 100644
--- a/chrome/test/data/extensions/api_test/enterprise_platform_keys/background.js
+++ b/chrome/test/data/extensions/api_test/enterprise_platform_keys/background.js
@@ -435,174 +435,167 @@
 }
 
 // Verifies that signing data with RSA |keyPair| works. Error messages will be
-// prefixed with |debugMessage|. Calls |callback| with the generated key pair
-// and the SubjectPublicKeyInfo for further operations.
-function verifyRsaKeySign(
-    token, algorithm, keyPair, spki, debugMessage, callback) {
-  var cachedSignature;
-
+// prefixed with |debugMessage|. Returns an array with the first element as the
+// generated key pair and the second element as the SubjectPublicKeyInfo.
+async function verifyRsaKeySign(token, algorithm, keyPair, spki, debugMessage) {
   const SIGN_PARAMS = {name: 'RSASSA-PKCS1-v1_5'};
-  token.subtleCrypto.sign(SIGN_PARAMS, keyPair.privateKey, DATA)
-      .then(
-          callbackPass(function(signature) {
-            var importParams = {
-              name: algorithm.name,
-              // RsaHashedImportParams
-              hash: {
-                name: algorithm.hash.name,
-              }
-            };
-            assertTrue(!!signature, debugMessage + ': No signature.');
-            assertTrue(
-                signature.length != 0, debugMessage + ': Signature is empty.');
-            cachedSignature = signature;
-            return window.crypto.subtle.importKey(
-                'spki', spki, importParams, false, ['verify']);
-          }),
-          function(error) {
-            fail(debugMessage + ': Sign failed: ' + error);
-          })
-      .then(
-          callbackPass(function(webCryptoPublicKey) {
-            assertTrue(!!webCryptoPublicKey);
-            assertEq(
-                algorithm.modulusLength,
-                webCryptoPublicKey.algorithm.modulusLength);
-            assertEq(
-                algorithm.publicExponent,
-                webCryptoPublicKey.algorithm.publicExponent);
-            return window.crypto.subtle.verify(
-                algorithm, webCryptoPublicKey, cachedSignature, DATA);
-          }),
-          function(error) {
-            fail(debugMessage + ': Import failed: ' + error);
-          })
-      .then(
-          callbackPass(function(success) {
-            assertEq(true, success, debugMessage + ': Signature invalid.');
-            callback(keyPair, spki);
-          }),
-          function(error) {
-            fail(debugMessage + ': Verification failed: ' + error);
-          });
+
+  let signature;
+  try {
+    signature =
+        await token.subtleCrypto.sign(SIGN_PARAMS, keyPair.privateKey, DATA);
+  } catch (error) {
+    fail(debugMessage + ': Sign failed: ' + error);
+  }
+
+  var importParams = {
+    name: algorithm.name,
+    // RsaHashedImportParams
+    hash: {
+      name: algorithm.hash.name,
+    }
+  };
+  assertTrue(!!signature, debugMessage + ': No signature.');
+  assertTrue(signature.length != 0, debugMessage + ': Signature is empty.');
+
+  let webCryptoPublicKey;
+  try {
+    webCryptoPublicKey = await window.crypto.subtle.importKey(
+        'spki', spki, importParams, false, ['verify']);
+  } catch (error) {
+    fail(debugMessage + ': Import failed: ' + error);
+  }
+
+  assertTrue(!!webCryptoPublicKey);
+  assertEq(algorithm.modulusLength, webCryptoPublicKey.algorithm.modulusLength);
+  assertEq(
+      algorithm.publicExponent, webCryptoPublicKey.algorithm.publicExponent);
+
+  let success;
+  try {
+    success = await window.crypto.subtle.verify(
+        algorithm, webCryptoPublicKey, signature, DATA);
+  } catch (error) {
+    fail(debugMessage + ': Verification failed: ' + error);
+  }
+
+  assertEq(true, success, debugMessage + ': Signature invalid.');
+  return [keyPair, spki];
 }
 
 // Verifies that signing data with EC |keyPair| works. Error messages will be
-// prefixed with |debugMessage|. Calls |callback| with the generated key pair
-// and the SubjectPublicKeyInfo for further operations.
-function verifyEcKeySign(token, params, keyPair, spki, debugMessage, callback) {
-  var cachedSignature;
-  token.subtleCrypto.sign(params.sign, keyPair.privateKey, DATA)
-      .then(
-          callbackPass(function(signature) {
-            assertTrue(!!signature, debugMessage + ': No signature.');
-            assertTrue(
-                signature.length != 0, debugMessage + ': Signature is empty.');
-            cachedSignature = signature;
-            return window.crypto.subtle.importKey(
-                'spki', spki, params.importKey, false, ['verify']);
-          }),
-          function(error) {
-            fail(debugMessage + ': Sign failed: ' + error);
-          })
-      .then(
-          callbackPass(function(webCryptoPublicKey) {
-            assertTrue(!!webCryptoPublicKey);
-            return window.crypto.subtle.verify(
-                params.verify, webCryptoPublicKey, cachedSignature, DATA);
-          }),
-          function(error) {
-            fail(debugMessage + ': Import failed: ' + error);
-          })
-      .then(
-          callbackPass(function(success) {
-            assertEq(true, success, debugMessage + ': Signature invalid.');
-            callback(keyPair, spki);
-          }),
-          function(error) {
-            fail(debugMessage + ': Verification failed: ' + error);
-          });
+// prefixed with |debugMessage|. Returns an array with the first element as the
+// generated key pair and the second element as the SubjectPublicKeyInfo.
+async function verifyEcKeySign(token, params, keyPair, spki, debugMessage) {
+  let signature;
+  try {
+    signature =
+        await token.subtleCrypto.sign(params.sign, keyPair.privateKey, DATA);
+  } catch (error) {
+    fail(debugMessage + ': Sign failed: ' + error);
+  }
+
+  assertTrue(!!signature, debugMessage + ': No signature.');
+  assertTrue(signature.length != 0, debugMessage + ': Signature is empty.');
+
+  let webCryptoPublicKey;
+  try {
+    webCryptoPublicKey = await window.crypto.subtle.importKey(
+        'spki', spki, params.importKey, false, ['verify']);
+  } catch (error) {
+    fail(debugMessage + ': Import failed: ' + error);
+  }
+
+  assertTrue(!!webCryptoPublicKey);
+
+  let success;
+  try {
+    success = await window.crypto.subtle.verify(
+        params.verify, webCryptoPublicKey, signature, DATA);
+  } catch (error) {
+    fail(debugMessage + ': Verification failed: ' + error);
+  }
+
+  assertEq(true, success, debugMessage + ': Signature invalid.');
+  return [keyPair, spki];
 }
 
 // Generates an RSA key with the |algorithm| parameters. Signs random data using
-// the new key and verifies the signature using WebCrypto. Calls |callback| with
-// the generated key pair and the SubjectPublicKeyInfo for further operations.
-// Also freezes |algorithm|.
-function generateRsaKeyAndVerify(token, algorithm, callback) {
+// the new key and verifies the signature using WebCrypto. Returns an array with
+// the first element as the generated key pair and the second element as the
+// SubjectPublicKeyInfo. Also freezes |algorithm|.
+async function generateRsaKeyAndVerify(token, algorithm) {
   // Ensure that this algorithm object is not modified, so that later
   // comparisons really do the right thing.
   Object.freeze(algorithm.hash);
   Object.freeze(algorithm);
 
-  var cachedKeyPair;
-  token.subtleCrypto.generateKey(algorithm, false, ['sign'])
-      .then(
-          callbackPass(function(keyPair) {
-            assertTrue(!!keyPair, 'No key pair.');
-            cachedKeyPair = keyPair;
-            return token.subtleCrypto.exportKey('spki', keyPair.publicKey);
-          }),
-          function(error) {
-            fail('GenerateKey failed: ' + error);
-          })
-      .then(
-          callbackPass(function(publicKeySpki) {
-            // Ensure that the returned key pair has the expected format.
-            // Some parameter independent checks:
-            checkRsaKeyPairCommonFormat(cachedKeyPair);
+  let keyPair;
+  try {
+    keyPair = await token.subtleCrypto.generateKey(algorithm, false, ['sign']);
+  } catch (error) {
+    fail('GenerateKey failed: ' + error);
+  }
+  assertTrue(!!keyPair, 'No key pair.');
 
-            // Checks depending on the generateKey arguments:
-            var privateKey = cachedKeyPair.privateKey;
-            assertEq(['sign'], privateKey.usages);
-            assertEq(algorithm, privateKey.algorithm);
+  let publicKeySpki;
+  try {
+    publicKeySpki =
+        await token.subtleCrypto.exportKey('spki', keyPair.publicKey);
+  } catch (error) {
+    fail('Export failed: ' + error);
+  }
 
-            var publicKey = cachedKeyPair.publicKey;
-            assertEq([], publicKey.usages);
-            assertEq(algorithm, publicKey.algorithm);
+  // Ensure that the returned key pair has the expected format.
+  // Some parameter independent checks:
+  checkRsaKeyPairCommonFormat(keyPair);
 
-            verifyRsaKeySign(
-                token, algorithm, cachedKeyPair, publicKeySpki,
-                /*debugMessage=*/ 'First signing attempt',
-                callbackPass(callback));
-          }),
-          function(error) {
-            fail('Export failed: ' + error);
-          });
+  // Checks depending on the generateKey arguments:
+  var privateKey = keyPair.privateKey;
+  assertEq(['sign'], privateKey.usages);
+  assertEq(algorithm, privateKey.algorithm);
+
+  var publicKey = keyPair.publicKey;
+  assertEq([], publicKey.usages);
+  assertEq(algorithm, publicKey.algorithm);
+
+  return verifyRsaKeySign(
+      token, algorithm, keyPair, publicKeySpki,
+      /*debugMessage=*/ 'First signing attempt');
 }
 
-function generateEcKeyAndVerify(token, params, callback) {
-  var cachedKeyPair;
-  token.subtleCrypto.generateKey(params.generateKey, false, ['sign'])
-      .then(
-          callbackPass(function(keyPair) {
-            assertTrue(!!keyPair, 'No key pair.');
-            cachedKeyPair = keyPair;
-            return token.subtleCrypto.exportKey('spki', keyPair.publicKey);
-          }),
-          function(error) {
-            fail('GenerateKey failed: ' + error);
-          })
-      .then(
-          callbackPass(function(publicKeySpki) {
-            // Ensure that the returned key pair has the expected format.
-            // Some parameter independent checks:
-            checkEcKeyPairCommonFormat(cachedKeyPair);
+async function generateEcKeyAndVerify(token, params) {
+  let keyPair;
+  try {
+    keyPair = await token.subtleCrypto.generateKey(
+        params.generateKey, false, ['sign']);
+  } catch (error) {
+    fail('GenerateKey failed: ' + error);
+  }
+  assertTrue(!!keyPair, 'No key pair.');
 
-            // Checks depending on the generateKey arguments:
-            var privateKey = cachedKeyPair.privateKey;
-            assertEq(['sign'], privateKey.usages);
+  let publicKeySpki;
+  try {
+    publicKeySpki =
+        await token.subtleCrypto.exportKey('spki', keyPair.publicKey);
+  } catch (error) {
+    fail('Export failed: ' + error);
+  }
 
-            var publicKey = cachedKeyPair.publicKey;
-            assertEq([], publicKey.usages);
+  // Ensure that the returned key pair has the expected format.
+  // Some parameter independent checks:
+  checkEcKeyPairCommonFormat(keyPair);
 
-            verifyEcKeySign(
-                token, params, cachedKeyPair, publicKeySpki,
-                /*debugMessage=*/ 'First signing attempt',
-                callbackPass(callback));
-          }),
-          function(error) {
-            fail('Export failed: ' + error);
-          });
+  // Checks depending on the generateKey arguments:
+  var privateKey = keyPair.privateKey;
+  assertEq(['sign'], privateKey.usages);
+
+  var publicKey = keyPair.publicKey;
+  assertEq([], publicKey.usages);
+
+  return verifyEcKeySign(
+      token, params, keyPair, publicKeySpki,
+      /*debugMessage=*/ 'First signing attempt');
 }
 
 
@@ -635,36 +628,37 @@
 
 // Generates an RSA key pair and signs some data with it. Verifies the signature
 // using WebCrypto. Verifies also that a second sign operation fails.
-function testGenerateRsaKeyAndSignAllowedOnce(token) {
-  generateRsaKeyAndVerify(
-      token, RSA_ALGORITHM, callbackPass(function(keyPair, spki) {
-        // Try to sign data with the same key a second time, which
-        // must fail.
-        var signParams = {name: 'RSASSA-PKCS1-v1_5'};
-        token.subtleCrypto.sign(signParams, keyPair.privateKey, DATA)
-            .then(function(signature) {
-              fail('Second sign call was expected to fail.');
-            }, callbackPass(function(error) {
-                    assertTrue(error instanceof Error);
-                    assertEq(
-                        'The operation failed for an operation-specific reason',
-                        error.message);
-                  }));
-      }));
+async function testGenerateRsaKeyAndSignAllowedOnce(token) {
+  const [keyPair, spki] = await generateRsaKeyAndVerify(token, RSA_ALGORITHM);
+
+  // Try to sign data with the same key a second time, which
+  // must fail.
+  var signParams = {name: 'RSASSA-PKCS1-v1_5'};
+
+  let signature;
+  try {
+    signature =
+        await token.subtleCrypto.sign(signParams, keyPair.privateKey, DATA);
+    fail('Second sign call was expected to fail.');
+  } catch (error) {
+    assertTrue(error instanceof Error);
+    assertEq(
+        'The operation failed for an operation-specific reason', error.message);
+    succeed();
+  }
 }
 
 // Generates an RSA key pair and signs some data with it. Verifies the signature
 // using WebCrypto. Verifies also that a second sign operation succeeds.
-function testGenerateRsaKeyAndSignAllowedMultipleTimes(token) {
-  generateRsaKeyAndVerify(
-      token, RSA_ALGORITHM, callbackPass(function(keyPair, spki) {
-        // Try to sign data with the same key a second time, which
-        // must succeed.
-        verifyRsaKeySign(
-            token, RSA_ALGORITHM, keyPair, spki,
-            /*debugMessage=*/ 'Second signing attempt',
-            callbackPass(function(keyPair, spki) {}));
-      }));
+async function testGenerateRsaKeyAndSignAllowedMultipleTimes(token) {
+  const [keyPair, spki] = await generateRsaKeyAndVerify(token, RSA_ALGORITHM);
+
+  // Try to sign data with the same key a second time, which
+  // must succeed.
+  await verifyRsaKeySign(
+      token, RSA_ALGORITHM, keyPair, spki,
+      /*debugMessage=*/ 'Second signing attempt');
+  succeed();
 }
 
 // Web Crypto ECDSA Operation Params.
@@ -695,39 +689,40 @@
 // Generates an elliptic curve (EC) key pair and signs some data with it.
 // Verifies the signature using WebCrypto. Verifies also that a second sign
 // operation fails.
-function testGenerateEcKeyAndSignAllowedOnce(token) {
-  generateEcKeyAndVerify(
-      token, ALL_ECDSA_PARAMS, callbackPass(function(keyPair, spki) {
-        // Try to sign data with the same key a second time, which
-        // must fail.
-        token.subtleCrypto.sign(ALL_ECDSA_PARAMS.sign, keyPair.privateKey, DATA)
-            .then(function(signature) {
-              fail('Second sign call was expected to fail.');
-            }, callbackPass(function(error) {
-                    assertTrue(error instanceof Error);
-                    assertEq(
-                        'The operation failed for an operation-specific reason',
-                        error.message);
-                  }));
-      }));
+async function testGenerateEcKeyAndSignAllowedOnce(token) {
+  const [keyPair, spki] = await generateEcKeyAndVerify(token, ALL_ECDSA_PARAMS);
+
+  let signature;
+  try {
+    // Try to sign data with the same key a second time, which
+    // must fail.
+    signature = await token.subtleCrypto.sign(
+        ALL_ECDSA_PARAMS.sign, keyPair.privateKey, DATA);
+    fail('Second sign call was expected to fail.');
+  } catch (error) {
+    assertTrue(error instanceof Error);
+    assertEq(
+        'The operation failed for an operation-specific reason', error.message);
+    succeed();
+  }
 }
 
 // Generates an elliptic curve (EC) key pair and signs some data with it.
 // Verifies the signature using WebCrypto. Verifies also that a second sign
 // operation succeeds.
-function testGenerateEcKeyAndSignAllowedMultipleTimes(token) {
-  generateEcKeyAndVerify(
-      token, ALL_ECDSA_PARAMS, callbackPass(function(keyPair, spki) {
-        verifyEcKeySign(
-            token, ALL_ECDSA_PARAMS, keyPair, spki,
-            /*debugMessage=*/ 'Second signing attempt',
-            callbackPass(function(keyPair, spki) {}));
-      }));
+async function testGenerateEcKeyAndSignAllowedMultipleTimes(token) {
+  const [keyPair, spki] = await generateEcKeyAndVerify(token, ALL_ECDSA_PARAMS);
+
+  // Try to sign data with the same key a second time, which must succeed.
+  await verifyEcKeySign(
+      token, ALL_ECDSA_PARAMS, keyPair, spki,
+      /*debugMessage=*/ 'Second signing attempt');
+  succeed();
 }
 
 // Generates a key and signs some data with other parameters. Verifies the
 // signature using WebCrypto.
-function testGenerateKeyAndSignOtherParameters(token) {
+async function testGenerateKeyAndSignOtherParameters(token) {
   var algorithm = {
     name: "RSASSA-PKCS1-v1_5",
     // RsaHashedKeyGenParams
@@ -739,13 +734,14 @@
     }
   };
 
-  generateRsaKeyAndVerify(
-      token, algorithm, callbackPass(function(keyPair, spki) {}));
+  await generateRsaKeyAndVerify(token, algorithm);
+
+  succeed();
 }
 
 // Call generate key with invalid algorithm parameter, missing
 // modulusLength.
-function testAlgorithmParameterMissingModulusLength(token) {
+async function testAlgorithmParameterMissingModulusLength(token) {
   var algorithm = {
     name: "RSASSA-PKCS1-v1_5",
     // Equivalent to 65537
@@ -754,46 +750,55 @@
       name: "SHA-1",
     }
   };
-  token.subtleCrypto.generateKey(algorithm, false, ['sign'])
-      .then(function(keyPair) { fail('generateKey was expected to fail'); },
-            callbackPass(function(error) {
+
+  try {
+    await token.subtleCrypto.generateKey(algorithm, false, ['sign']);
+    fail('generateKey was expected to fail');
+  } catch (error) {
     assertTrue(error instanceof Error);
     assertEq('A required parameter was missing or out-of-range', error.message);
-  }));
+    succeed();
+  }
 }
 
 // Call generate key with invalid algorithm parameter, missing hash.
-function testAlgorithmParameterMissingHash(token) {
+async function testAlgorithmParameterMissingHash(token) {
   var algorithm = {
     name: 'RSASSA-PKCS1-v1_5',
     modulusLength: 512,
     // Equivalent to 65537
     publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
   };
-  token.subtleCrypto.generateKey(algorithm, false, ['sign'])
-      .then(function(keyPair) { fail('generateKey was expected to fail'); },
-            callbackPass(function(error) {
+
+  try {
+    await token.subtleCrypto.generateKey(algorithm, false, ['sign']);
+    fail('generateKey was expected to fail');
+  } catch (error) {
     assertEq(
         new Error('Error: A required parameter was missing our out-of-range'),
         error);
-  }));
+    succeed();
+  }
 }
 
 // Call generate key with invalid algorithm parameter, unsupported public
 // exponent.
-function testAlgorithmParameterUnsupportedPublicExponent(token) {
+async function testAlgorithmParameterUnsupportedPublicExponent(token) {
   var algorithm = {
     name: 'RSASSA-PKCS1-v1_5',
     modulusLength: 512,
                    // Different from 65537.
     publicExponent: new Uint8Array([0x01, 0x01]),
   };
-  token.subtleCrypto.generateKey(algorithm, false, ['sign'])
-      .then(function(keyPair) { fail('generateKey was expected to fail'); },
-            callbackPass(function(error) {
+
+  try {
+    await token.subtleCrypto.generateKey(algorithm, false, ['sign']);
+    fail('generateKey was expected to fail');
+  } catch (error) {
     assertTrue(error instanceof Error);
     assertEq('A required parameter was missing or out-of-range', error.message);
-  }));
+    succeed();
+  }
 }
 
 function testImportInvalidCert(token) {
diff --git a/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_test.js b/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_test.js
index 9815b5e..62e98f406 100644
--- a/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_test.js
+++ b/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_test.js
@@ -55,13 +55,15 @@
     assert(customElements.get('emoji-picker'));
   });
 
-  test('first tab should be active by default', () => {
-    const button = findInEmojiPicker('emoji-group-button:first-child', 'div');
+  test('first non-chevron tab should be active by default', () => {
+    const button =
+        findInEmojiPicker('emoji-group-button[data-group="history"]', 'div');
     assertTrue(isGroupButtonActive(button));
   });
 
-  test('second tab should be inactive by default', () => {
-    const button = findInEmojiPicker('emoji-group-button:nth-child(2)', 'div');
+  test('second non-chevron tab should be inactive by default', () => {
+    const button =
+        findInEmojiPicker('emoji-group-button[data-group="1"]', 'div');
     assertFalse(isGroupButtonActive(button));
   });
 
@@ -70,20 +72,23 @@
     const emojiGroups = findInEmojiPicker('#groups');
     const initialScroll = emojiGroups.scrollTop;
 
+    // History group doesn't exist when there is no history, so scrolling to
+    // the first non-history group (0) may not trigger a scroll, so scroll to
+    // group (1).
     const firstButton =
         findInEmojiPicker('emoji-group-button[data-group="history"]', 'div');
-    const secondButton =
+    const thirdButton =
         findInEmojiPicker('emoji-group-button[data-group="1"]', 'div');
 
     // wait so emoji-groups render and we have something to scroll to.
     await waitForCondition(
         () => findInEmojiPicker(
-            '[data-group="1"] > emoji-group', 'emoji-button', 'button'));
-    secondButton.click();
+            '[data-group="5"] > emoji-group', 'emoji-button', 'button'));
+    thirdButton.click();
 
     // wait while waiting for scroll to happen and update buttons.
     await waitForCondition(
-        () => isGroupButtonActive(secondButton) &&
+        () => isGroupButtonActive(thirdButton) &&
             !isGroupButtonActive(firstButton));
 
     const newScroll = emojiGroups.scrollTop;
diff --git a/chrome/test/data/webui/js/custom_element_test.js b/chrome/test/data/webui/js/custom_element_test.js
new file mode 100644
index 0000000..5b2b043
--- /dev/null
+++ b/chrome/test/data/webui/js/custom_element_test.js
@@ -0,0 +1,40 @@
+// 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 {CustomElement} from 'chrome://resources/js/custom_element.js';
+
+import {assertEquals, assertTrue} from '../chai_assert.js';
+
+class TestElement extends CustomElement {
+  static get template() {
+    return '<div id="content"></div>';
+  }
+}
+
+customElements.define('test-element', TestElement);
+
+let testElement;
+
+suite('CustomElementTest', function() {
+  setup(function() {
+    testElement = document.createElement('test-element');
+    document.body.appendChild(testElement);
+  });
+
+  test('Template', function() {
+    assertEquals(TestElement.template, testElement.shadowRoot.innerHTML);
+  });
+
+  test('Test $()', function() {
+    assertTrue(
+        testElement.$('#content') ===
+        testElement.shadowRoot.getElementById('content'));
+  });
+
+  test('Test $all()', function() {
+    assertTrue(
+        testElement.$all('#content')[0] ===
+        testElement.shadowRoot.getElementById('content'));
+  });
+});
diff --git a/chrome/test/data/webui/js/webui_resource_module_async_browsertest.js b/chrome/test/data/webui/js/webui_resource_module_async_browsertest.js
index eaf6fc46..1de7eb5 100644
--- a/chrome/test/data/webui/js/webui_resource_module_async_browsertest.js
+++ b/chrome/test/data/webui/js/webui_resource_module_async_browsertest.js
@@ -122,3 +122,14 @@
 TEST_F('ColorUtilsModuleTest', 'All', function() {
   mocha.run();
 });
+
+var CustomElementModuleTest = class extends WebUIResourceModuleAsyncTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://test/test_loader.html?module=js/custom_element_test.js';
+  }
+};
+
+TEST_F('CustomElementModuleTest', 'All', function() {
+  mocha.run();
+});
diff --git a/chrome/test/data/webui/settings/sync_account_control_test.js b/chrome/test/data/webui/settings/sync_account_control_test.js
index e50b86f..fbc4b00 100644
--- a/chrome/test/data/webui/settings/sync_account_control_test.js
+++ b/chrome/test/data/webui/settings/sync_account_control_test.js
@@ -344,7 +344,7 @@
     assertFalse(displayedText.includes('barName'));
     assertFalse(displayedText.includes('fooName'));
     assertFalse(displayedText.includes('Sync isn\'t working'));
-    assertTrue(displayedText.includes('Error syncing passwords'));
+    assertTrue(displayedText.includes('Password sync isn\'t working'));
     // The sync error button is shown to resolve the error.
     assertTrue(isChildVisible(testElement, '#sync-error-button'));
     assertTrue(isChildVisible(testElement, '#turn-off'));
diff --git a/chrome/test/data/webui/tab_search/tab_search_app_test.js b/chrome/test/data/webui/tab_search/tab_search_app_test.js
index e2a692b..51f7a68 100644
--- a/chrome/test/data/webui/tab_search/tab_search_app_test.js
+++ b/chrome/test/data/webui/tab_search/tab_search_app_test.js
@@ -241,6 +241,7 @@
       title: 'Example',
       url: 'https://example.com',
       lastActiveTimeTicks: {internalValue: BigInt(5)},
+      lastActiveElapsedText: '',
     });
     testProxy.getCallbackRouterRemote().tabUpdated(updatedTab);
     await flushTasks();
@@ -419,6 +420,7 @@
         title: 'Google',
         url: 'https://www.google.com',
         lastActiveTimeTicks: {internalValue: BigInt(2)},
+        lastActiveElapsedText: '',
       },
       {
         index: 1,
@@ -426,6 +428,7 @@
         title: 'Bing',
         url: 'https://www.bing.com',
         lastActiveTimeTicks: {internalValue: BigInt(4)},
+        lastActiveElapsedText: '',
         active: true,
       },
       {
@@ -434,6 +437,7 @@
         title: 'Yahoo',
         url: 'https://www.yahoo.com',
         lastActiveTimeTicks: {internalValue: BigInt(3)},
+        lastActiveElapsedText: '',
       }
     ];
 
diff --git a/chrome/test/data/webui/tab_search/tab_search_test_data.js b/chrome/test/data/webui/tab_search/tab_search_test_data.js
index cc878fa..d3d6c762 100644
--- a/chrome/test/data/webui/tab_search/tab_search_test_data.js
+++ b/chrome/test/data/webui/tab_search/tab_search_test_data.js
@@ -17,6 +17,7 @@
             title: 'Google',
             url: 'https://www.google.com',
             lastActiveTimeTicks: {internalValue: BigInt(5)},
+            lastActiveElapsedText: '',
           },
           {
             index: 1,
@@ -24,6 +25,7 @@
             title: 'Amazon',
             url: 'https://www.amazon.com',
             lastActiveTimeTicks: {internalValue: BigInt(4)},
+            lastActiveElapsedText: '',
           },
           {
             index: 2,
@@ -31,6 +33,7 @@
             title: 'Apple',
             url: 'https://www.apple.com',
             lastActiveTimeTicks: {internalValue: BigInt(3)},
+            lastActiveElapsedText: '',
           },
         ],
       },
@@ -44,6 +47,7 @@
             title: 'Bing',
             url: 'https://www.bing.com/',
             lastActiveTimeTicks: {internalValue: BigInt(2)},
+            lastActiveElapsedText: '',
           },
           {
             index: 1,
@@ -51,6 +55,7 @@
             title: 'Yahoo',
             url: 'https://www.yahoo.com',
             lastActiveTimeTicks: {internalValue: BigInt(1)},
+            lastActiveElapsedText: '',
           },
           {
             index: 2,
@@ -58,6 +63,7 @@
             title: 'Apple',
             url: 'https://www.apple.com/',
             lastActiveTimeTicks: {internalValue: BigInt(0)},
+            lastActiveElapsedText: '',
           },
         ],
       }
@@ -86,6 +92,7 @@
       title: siteName,
       url: 'https://www.' + siteName.toLowerCase() + '.com',
       lastActiveTimeTicks: siteNames.length - i,
+      lastActiveElapsedText: '',
     };
   });
 }
diff --git a/chrome/updater/mac/signing/BUILD.gn b/chrome/updater/mac/signing/BUILD.gn
index 470657cf..544ec5e 100644
--- a/chrome/updater/mac/signing/BUILD.gn
+++ b/chrome/updater/mac/signing/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/features.gni")
+import("//build/config/mac/base_rules.gni")
 import("//build/util/branding.gni")
 import("//build/util/version.gni")
 import("//chrome/installer/mac/mac_signing_sources.gni")
diff --git a/chrome/updater/test/test_app/BUILD.gn b/chrome/updater/test/test_app/BUILD.gn
index 51b708a..a4f2f75 100644
--- a/chrome/updater/test/test_app/BUILD.gn
+++ b/chrome/updater/test/test_app/BUILD.gn
@@ -2,14 +2,11 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/mac/rules.gni")
 import("//build/util/process_version.gni")
 import("//build/util/version.gni")
 import("//chrome/updater/branding.gni")
 
-if (is_mac) {
-  import("//build/config/mac/rules.gni")
-}
-
 app_name = "UpdaterTestApp"
 app_bundle_id = "org.chromium.updatertestapp"
 
diff --git a/chromecast/renderer/cast_content_renderer_client.cc b/chromecast/renderer/cast_content_renderer_client.cc
index fcc0ccf..cc3fdbf3 100644
--- a/chromecast/renderer/cast_content_renderer_client.cc
+++ b/chromecast/renderer/cast_content_renderer_client.cc
@@ -74,15 +74,18 @@
 }  // namespace
 
 #if defined(OS_ANDROID)
-// Audio renderer algorithm maximum capacity.
+// Audio renderer algorithm maximum capacity. 5s buffer is already large enough,
+// we don't need a larger capacity. Otherwise audio renderer will double the
+// buffer size when underrun happens, which will cause the playback paused to
+// wait long time for enough buffers.
 constexpr base::TimeDelta kAudioRendererMaxCapacity =
-    base::TimeDelta::FromSeconds(10);
+    base::TimeDelta::FromSeconds(5);
 // Audio renderer algorithm starting capacity.  Configure large enough to
 // prevent underrun.
 constexpr base::TimeDelta kAudioRendererStartingCapacity =
-    base::TimeDelta::FromMilliseconds(5000);
+    base::TimeDelta::FromSeconds(5);
 constexpr base::TimeDelta kAudioRendererStartingCapacityEncrypted =
-    base::TimeDelta::FromMilliseconds(5500);
+    base::TimeDelta::FromSeconds(5);
 #endif  // defined(OS_ANDROID)
 
 CastContentRendererClient::CastContentRendererClient()
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 89f2fcf..1dc43c8 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-13839.0.0
\ No newline at end of file
+13840.0.0
\ No newline at end of file
diff --git a/chromeos/components/camera_app_ui/resources/js/gallerybutton.js b/chromeos/components/camera_app_ui/resources/js/gallerybutton.js
index 7af5214..7249f171 100644
--- a/chromeos/components/camera_app_ui/resources/js/gallerybutton.js
+++ b/chromeos/components/camera_app_ui/resources/js/gallerybutton.js
@@ -6,9 +6,9 @@
 import * as dom from './dom.js';
 import * as filesystem from './models/file_system.js';
 import {
-  NativeDirectoryEntry,  // eslint-disable-line no-unused-vars
-  NativeFileEntry,       // eslint-disable-line no-unused-vars
-} from './models/native_file_system_entry.js';
+  DirectoryAccessEntry,  // eslint-disable-line no-unused-vars
+  FileAccessEntry,       // eslint-disable-line no-unused-vars
+} from './models/file_system_access_entry.js';
 // eslint-disable-next-line no-unused-vars
 import {ResultSaver} from './models/result_saver.js';
 import {VideoSaver} from './models/video_saver.js';
@@ -26,12 +26,12 @@
  */
 class CoverPhoto {
   /**
-   * @param {!NativeFileEntry} file File entry of cover photo.
+   * @param {!FileAccessEntry} file File entry of cover photo.
    * @param {string} thumbnailUrl Url to its thumbnail.
    */
   constructor(file, thumbnailUrl) {
     /**
-     * @type {!NativeFileEntry}
+     * @type {!FileAccessEntry}
      * @const
      */
     this.file = file;
@@ -60,7 +60,7 @@
 
   /**
    * Creates CoverPhoto objects from photo file.
-   * @param {!NativeFileEntry} file
+   * @param {!FileAccessEntry} file
    * @return {!Promise<!CoverPhoto>}
    */
   static async create(file) {
@@ -96,7 +96,7 @@
 
     /**
      * Directory holding saved pictures showing in gallery.
-     * @type {?NativeDirectoryEntry}
+     * @type {?DirectoryAccessEntry}
      * @private
      */
     this.directory_ = null;
@@ -115,7 +115,7 @@
 
   /**
    * Initializes the gallery button.
-   * @param {!NativeDirectoryEntry} dir Directory holding saved pictures
+   * @param {!DirectoryAccessEntry} dir Directory holding saved pictures
    *     showing in gallery.
    */
   async initialize(dir) {
@@ -124,7 +124,7 @@
   }
 
   /**
-   * @param {?NativeFileEntry} file File to be set as cover photo.
+   * @param {?FileAccessEntry} file File to be set as cover photo.
    * @return {!Promise}
    * @private
    */
diff --git a/chromeos/components/camera_app_ui/resources/js/js.gni b/chromeos/components/camera_app_ui/resources/js/js.gni
index 60efeba..13786ba 100644
--- a/chromeos/components/camera_app_ui/resources/js/js.gni
+++ b/chromeos/components/camera_app_ui/resources/js/js.gni
@@ -36,7 +36,7 @@
   "models/load_time_data.js",
   "models/local_storage.js",
   "models/mp4_video_processor.js",
-  "models/native_file_system_entry.js",
+  "models/file_system_access_entry.js",
   "models/result_saver.js",
   "models/video_processor_interface.js",
   "models/video_saver.js",
diff --git a/chromeos/components/camera_app_ui/resources/js/models/file_system.js b/chromeos/components/camera_app_ui/resources/js/models/file_system.js
index cfb89ff..b3a363f 100644
--- a/chromeos/components/camera_app_ui/resources/js/models/file_system.js
+++ b/chromeos/components/camera_app_ui/resources/js/models/file_system.js
@@ -6,17 +6,18 @@
 import {WaitableEvent} from '../waitable_event.js';
 
 import {Filenamer, IMAGE_PREFIX, VIDEO_PREFIX} from './file_namer.js';
+import {
+  DirectoryAccessEntry,  // eslint-disable-line no-unused-vars
+  DirectoryAccessEntryImpl,
+  FileAccessEntry,  // eslint-disable-line no-unused-vars
+} from './file_system_access_entry.js';
 import * as idb from './idb.js';
 import {getMaybeLazyDirectory} from './lazy_directory_entry.js';
-import {
-  NativeDirectoryEntry,  // eslint-disable-line no-unused-vars
-  NativeDirectoryEntryImpl,
-  NativeFileEntry,  // eslint-disable-line no-unused-vars
-} from './native_file_system_entry.js';
+
 
 /**
  * Checks if the entry's name has the video prefix.
- * @param {!NativeFileEntry} entry File entry.
+ * @param {!FileAccessEntry} entry File entry.
  * @return {boolean} Has the video prefix or not.
  */
 export function hasVideoPrefix(entry) {
@@ -25,7 +26,7 @@
 
 /**
  * Checks if the entry's name has the image prefix.
- * @param {!NativeFileEntry} entry File entry.
+ * @param {!FileAccessEntry} entry File entry.
  * @return {boolean} Has the image prefix or not.
  */
 function hasImagePrefix(entry) {
@@ -34,19 +35,19 @@
 
 /**
  * Temporary directory in the internal file system.
- * @type {?NativeDirectoryEntry}
+ * @type {?DirectoryAccessEntry}
  */
 let internalTempDir = null;
 
 /**
  * Camera directory in the external file system.
- * @type {?NativeDirectoryEntry}
+ * @type {?DirectoryAccessEntry}
  */
 let cameraDir = null;
 
 /**
  * Gets camera directory used by CCA.
- * @return {?NativeDirectoryEntry}
+ * @return {?DirectoryAccessEntry}
  */
 export function getCameraDirectory() {
   return cameraDir;
@@ -54,15 +55,15 @@
 
 /**
  * Initializes the temporary directory in the internal file system.
- * @return {!Promise<!NativeDirectoryEntry>} Promise for the directory result.
+ * @return {!Promise<!DirectoryAccessEntry>} Promise for the directory result.
  */
 async function initInternalTempDir() {
-  return new NativeDirectoryEntryImpl(await navigator.storage.getDirectory());
+  return new DirectoryAccessEntryImpl(await navigator.storage.getDirectory());
 }
 
 /**
  * Initializes the camera directory in the external file system.
- * @return {!Promise<?NativeDirectoryEntry>} Promise for the directory result.
+ * @return {!Promise<?DirectoryAccessEntry>} Promise for the directory result.
  */
 async function initCameraDirectory() {
   const handle = new WaitableEvent();
@@ -90,7 +91,7 @@
     });
   }
   const dir = await handle.wait();
-  const myFilesDir = new NativeDirectoryEntryImpl(dir);
+  const myFilesDir = new DirectoryAccessEntryImpl(dir);
   return getMaybeLazyDirectory(myFilesDir, 'Camera');
 }
 
@@ -111,7 +112,7 @@
  * Saves photo blob or metadata blob into predefined default location.
  * @param {!Blob} blob Data of the photo to be saved.
  * @param {string} name Filename of the photo to be saved.
- * @return {!Promise<?NativeFileEntry>} Promise for the result.
+ * @return {!Promise<?FileAccessEntry>} Promise for the result.
  */
 export async function saveBlob(blob, name) {
   const file = await cameraDir.createFile(name);
@@ -123,7 +124,7 @@
 
 /**
  * Creates a file for saving video recording result.
- * @return {!Promise<!NativeFileEntry>} Newly created video file.
+ * @return {!Promise<!FileAccessEntry>} Newly created video file.
  * @throws {!Error} If failed to create video file.
  */
 export async function createVideoFile() {
@@ -141,7 +142,7 @@
 const PRIVATE_TEMPFILE_NAME = 'video-intent.mkv';
 
 /**
- * @return {!Promise<!NativeFileEntry>} Newly created temporary file.
+ * @return {!Promise<!FileAccessEntry>} Newly created temporary file.
  * @throws {!Error} If failed to create video temp file.
  */
 export async function createPrivateTempVideoFile() {
@@ -157,7 +158,7 @@
 
 /**
  * Gets the picture entries.
- * @return {!Promise<!Array<!NativeFileEntry>>} Promise for the picture
+ * @return {!Promise<!Array<!FileAccessEntry>>} Promise for the picture
  *     entries.
  */
 export async function getEntries() {
@@ -172,7 +173,7 @@
 
 /**
  * Returns an URL for a picture given by the file |entry|.
- * @param {!NativeFileEntry} entry The file entry of the picture.
+ * @param {!FileAccessEntry} entry The file entry of the picture.
  * @return {!Promise<string>} Promise for the result.
  */
 export async function pictureURL(entry) {
diff --git a/chromeos/components/camera_app_ui/resources/js/models/native_file_system_entry.js b/chromeos/components/camera_app_ui/resources/js/models/file_system_access_entry.js
similarity index 86%
rename from chromeos/components/camera_app_ui/resources/js/models/native_file_system_entry.js
rename to chromeos/components/camera_app_ui/resources/js/models/file_system_access_entry.js
index f4d624f..63921a32 100644
--- a/chromeos/components/camera_app_ui/resources/js/models/native_file_system_entry.js
+++ b/chromeos/components/camera_app_ui/resources/js/models/file_system_access_entry.js
@@ -10,7 +10,7 @@
 /**
  * The file system entry implementation for SWA.
  */
-export class NativeFileSystemEntry {
+export class FileSystemAccessEntry {
   /**
    * @param {!FileSystemHandle} handle
    */
@@ -33,7 +33,7 @@
 /**
  * The file entry implementation for SWA.
  */
-export class NativeFileEntry extends NativeFileSystemEntry {
+export class FileAccessEntry extends FileSystemAccessEntry {
   /**
    * @param {!FileSystemFileHandle} handle
    */
@@ -104,7 +104,7 @@
  * The abstract interface for the directory entry.
  * @interface
  */
-export class NativeDirectoryEntry {
+export class DirectoryAccessEntry {
   /* eslint-disable getter-return */
 
   /**
@@ -118,14 +118,14 @@
 
   /**
    * Gets files in this directory.
-   * @return {!Promise<!Array<!NativeFileEntry>>}
+   * @return {!Promise<!Array<!FileAccessEntry>>}
    * @abstract
    */
   async getFiles() {}
 
   /**
    * Gets directories in this directory.
-   * @return {!Promise<!Array<!NativeDirectoryEntry>>}
+   * @return {!Promise<!Array<!DirectoryAccessEntry>>}
    * @abstract
    */
   async getDirectories() {}
@@ -133,7 +133,7 @@
   /**
    * Gets the file given by its |name|.
    * @param {string} name The name of the file.
-   * @return {!Promise<?NativeFileEntry>} The entry of the found file.
+   * @return {!Promise<?FileAccessEntry>} The entry of the found file.
    * @abstract
    */
   async getFile(name) {}
@@ -143,7 +143,7 @@
    * name, it will try to use a name with index as suffix.
    * e.g. IMG.png => IMG (1).png
    * @param {string} name The name of the file.
-   * @return {!Promise<!NativeFileEntry>} The entry of the created file.
+   * @return {!Promise<!FileAccessEntry>} The entry of the created file.
    * @abstract
    */
   async createFile(name) {}
@@ -154,7 +154,7 @@
    * TODO(crbug.com/1127587): Split this method to getDirectory() and
    * createDirectory().
    * @param {{name: string, createIfNotExist: boolean}} params
-   * @return {!Promise<?NativeDirectoryEntry>} The entry of the found/created
+   * @return {!Promise<?DirectoryAccessEntry>} The entry of the found/created
    *     directory.
    */
   async getDirectory({name, createIfNotExist}) {}
@@ -162,9 +162,9 @@
 
 /**
  * The directory entry implementation for SWA.
- * @implements {NativeDirectoryEntry}
+ * @implements {DirectoryAccessEntry}
  */
-export class NativeDirectoryEntryImpl extends NativeFileSystemEntry {
+export class DirectoryAccessEntryImpl extends FileSystemAccessEntry {
   /**
    * @param {!FileSystemDirectoryHandle} handle
    */
@@ -189,7 +189,7 @@
    * @override
    */
   async getFiles() {
-    return /** @type {!Array<!NativeFileEntry>} */ (
+    return /** @type {!Array<!FileAccessEntry>} */ (
         await this.getHandles_({isDirectory: false}));
   }
 
@@ -197,7 +197,7 @@
    * @override
    */
   async getDirectories() {
-    return /** @type {!Array<!NativeDirectoryEntry>} */ (
+    return /** @type {!Array<!DirectoryAccessEntry>} */ (
         await this.getHandles_({isDirectory: true}));
   }
 
@@ -206,7 +206,7 @@
    */
   async getFile(name) {
     const handle = await this.handle_.getFileHandle(name, {create: false});
-    return new NativeFileEntry(handle);
+    return new FileAccessEntry(handle);
   }
 
   /**
@@ -242,7 +242,7 @@
       }
       const handle =
           await this.handle_.getFileHandle(uniqueName, {create: true});
-      return new NativeFileEntry(handle);
+      return new FileAccessEntry(handle);
     });
   }
 
@@ -254,7 +254,7 @@
       const handle = await this.handle_.getDirectoryHandle(
           name, {create: createIfNotExist});
       assert(handle !== null);
-      return new NativeDirectoryEntryImpl(
+      return new DirectoryAccessEntryImpl(
           /** @type {!FileSystemDirectoryHandle} */ (handle));
     } catch (error) {
       if (!createIfNotExist && error.name === 'NotFoundError') {
@@ -268,15 +268,15 @@
    * Gets the file handles in this directory if |isDirectory| is set to false.
    * If |isDirectory| is true, gets the directory entries instead.
    * @param {{isDirectory: boolean}} params
-   * @return {!Promise<!Array<!NativeFileSystemEntry>>}
+   * @return {!Promise<!Array<!FileSystemAccessEntry>>}
    */
   async getHandles_({isDirectory}) {
     const results = [];
     for await (const handle of this.handle_.values()) {
       if (isDirectory && handle.kind === 'directory') {
-        results.push(new NativeDirectoryEntryImpl(handle));
+        results.push(new DirectoryAccessEntryImpl(handle));
       } else if (!isDirectory && handle.kind === 'file') {
-        results.push(new NativeFileEntry(handle));
+        results.push(new FileAccessEntry(handle));
       }
     }
     return results;
diff --git a/chromeos/components/camera_app_ui/resources/js/models/lazy_directory_entry.js b/chromeos/components/camera_app_ui/resources/js/models/lazy_directory_entry.js
index 0f4f0f93..81a58f9 100644
--- a/chromeos/components/camera_app_ui/resources/js/models/lazy_directory_entry.js
+++ b/chromeos/components/camera_app_ui/resources/js/models/lazy_directory_entry.js
@@ -3,16 +3,16 @@
 // found in the LICENSE file.
 
 import {
-  NativeDirectoryEntry,  // eslint-disable-line no-unused-vars
-} from './native_file_system_entry.js';
+  DirectoryAccessEntry,  // eslint-disable-line no-unused-vars
+} from './file_system_access_entry.js';
 
 /**
  * Gets directory entry by given |name| under |parentDir| directory. If the
  * directory does not exist, returns a lazy directory which will only be created
  * once there is any file written in it.
- * @param {!NativeDirectoryEntry} parentDir Parent directory.
+ * @param {!DirectoryAccessEntry} parentDir Parent directory.
  * @param {string} name Name of the target directory.
- * @return {!Promise<!NativeDirectoryEntry>}
+ * @return {!Promise<!DirectoryAccessEntry>}
  */
 export async function getMaybeLazyDirectory(parentDir, name) {
   const targetDir =
@@ -24,16 +24,16 @@
 /**
  * A directory entry which will only create itself if there is any
  * file/directory created under it.
- * @implements {NativeDirectoryEntry}
+ * @implements {DirectoryAccessEntry}
  */
 class LazyDirectoryEntry {
   /**
-   * @param {!NativeDirectoryEntry} parentDirectory
+   * @param {!DirectoryAccessEntry} parentDirectory
    * @param {string} name
    */
   constructor(parentDirectory, name) {
     /**
-     * @type {!NativeDirectoryEntry}
+     * @type {!DirectoryAccessEntry}
      * @private
      */
     this.parent_ = parentDirectory;
@@ -45,13 +45,13 @@
     this.name_ = name;
 
     /**
-     * @type {?NativeDirectoryEntry}
+     * @type {?DirectoryAccessEntry}
      * @private
      */
     this.directory_ = null;
 
     /**
-     * @type {?Promise<!NativeDirectoryEntry>}
+     * @type {?Promise<!DirectoryAccessEntry>}
      * @private
      */
     this.creatingDirectory_ = null;
@@ -117,18 +117,18 @@
   /**
    * Gets the directory which this entry points to. Create it if it does not
    * exist.
-   * @return {!Promise<!NativeDirectoryEntry>}
+   * @return {!Promise<!DirectoryAccessEntry>}
    * @private
    */
   async getRealDirectory_() {
     if (this.creatingDirectory_ === null) {
       this.creatingDirectory_ =
-          (async () => /** @type {!NativeDirectoryEntry} */ (
+          (async () => /** @type {!DirectoryAccessEntry} */ (
                await this.parent_.getDirectory(
                    {name: this.name_, createIfNotExist: true})))();
     }
     this.directory_ =
-        /** @type {!NativeDirectoryEntry} */ (await this.creatingDirectory_);
+        /** @type {!DirectoryAccessEntry} */ (await this.creatingDirectory_);
     return this.directory_;
   }
 }
diff --git a/chromeos/components/camera_app_ui/resources/js/models/video_saver.js b/chromeos/components/camera_app_ui/resources/js/models/video_saver.js
index 10ee343..1e29c37a 100644
--- a/chromeos/components/camera_app_ui/resources/js/models/video_saver.js
+++ b/chromeos/components/camera_app_ui/resources/js/models/video_saver.js
@@ -11,7 +11,7 @@
 import {AsyncWriter} from './async_writer.js';
 import {createPrivateTempVideoFile} from './file_system.js';
 // eslint-disable-next-line no-unused-vars
-import {NativeFileEntry} from './native_file_system_entry.js';
+import {FileAccessEntry} from './file_system_access_entry.js';
 // eslint-disable-next-line no-unused-vars
 import {VideoProcessor} from './video_processor_interface.js';
 
@@ -52,12 +52,12 @@
  */
 export class VideoSaver {
   /**
-   * @param {!NativeFileEntry} file
+   * @param {!FileAccessEntry} file
    * @param {!VideoProcessor} processor
    */
   constructor(file, processor) {
     /**
-     * @const {!NativeFileEntry}
+     * @const {!FileAccessEntry}
      */
     this.file_ = file;
 
@@ -77,7 +77,7 @@
 
   /**
    * Finishes the write of video data parts and returns result video file.
-   * @return {!Promise<!NativeFileEntry>} Result video file.
+   * @return {!Promise<!FileAccessEntry>} Result video file.
    */
   async endWrite() {
     await this.processor_.close();
@@ -86,7 +86,7 @@
 
   /**
    * Creates video saver for the given file.
-   * @param {!NativeFileEntry} file
+   * @param {!FileAccessEntry} file
    * @return {!Promise<!VideoSaver>}
    */
   static async createForFile(file) {
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/review_result.js b/chromeos/components/camera_app_ui/resources/js/views/camera/review_result.js
index 109fabb..dbc009b6 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera/review_result.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera/review_result.js
@@ -5,7 +5,7 @@
 import * as dom from '../../dom.js';
 import {pictureURL} from '../../models/file_system.js';
 // eslint-disable-next-line no-unused-vars
-import {NativeFileEntry} from '../../models/native_file_system_entry.js';
+import {FileAccessEntry} from '../../models/file_system_access_entry.js';
 import * as state from '../../state.js';
 import * as util from '../../util.js';
 
@@ -132,7 +132,7 @@
 
   /**
    * Opens video result file and shows video on review result UI.
-   * @param {!NativeFileEntry} fileEntry Video result file.
+   * @param {!FileAccessEntry} fileEntry Video result file.
    * @return {!Promise<boolean>} Promise resolved with whether user confirms
    *     with the video result.
    */
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera_intent.js b/chromeos/components/camera_app_ui/resources/js/views/camera_intent.js
index 3fdcdc1..e1dd340 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera_intent.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera_intent.js
@@ -14,7 +14,7 @@
 import {Intent} from '../intent.js';
 import * as metrics from '../metrics.js';
 // eslint-disable-next-line no-unused-vars
-import {NativeFileEntry} from '../models/native_file_system_entry.js';
+import {FileAccessEntry} from '../models/file_system_access_entry.js';
 // eslint-disable-next-line no-unused-vars
 import {ResultSaver} from '../models/result_saver.js';
 import {VideoSaver} from '../models/video_saver.js';
@@ -93,7 +93,7 @@
     this.videoResult_ = null;
 
     /**
-     * @type {?NativeFileEntry}
+     * @type {?FileAccessEntry}
      * @private
      */
     this.videoResultFile_ = null;
diff --git a/chromeos/components/camera_app_ui/resources/strings/BUILD.gn b/chromeos/components/camera_app_ui/resources/strings/BUILD.gn
index 9c5f1c0..35014c8 100644
--- a/chromeos/components/camera_app_ui/resources/strings/BUILD.gn
+++ b/chromeos/components/camera_app_ui/resources/strings/BUILD.gn
@@ -11,7 +11,7 @@
   defines = chrome_grit_defines
 
   outputs = [ "grit/chromeos_camera_app_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "chromeos_camera_app_strings_$locale.pak" ]
   }
 }
diff --git a/chromeos/crosapi/mojom/account_manager.mojom b/chromeos/crosapi/mojom/account_manager.mojom
index 0af961b3..73082f4 100644
--- a/chromeos/crosapi/mojom/account_manager.mojom
+++ b/chromeos/crosapi/mojom/account_manager.mojom
@@ -4,6 +4,8 @@
 
 module crosapi.mojom;
 
+import "mojo/public/mojom/base/time.mojom";
+
 // Types of accounts which can be stored in Account Manager.
 // This must be kept in sync with
 // //ash/components/account_manager/tokens.proto
@@ -96,6 +98,27 @@
   GoogleServiceAuthError? error@2;
 };
 
+// Next ordinal value: 3
+[Stable]
+struct AccessTokenInfo {
+  // OAuth2 access token.
+  string access_token@0;
+
+  // Expiration time of `access_token`. This value has a built-in safety margin
+  // so it can be used as-is.
+  mojo_base.mojom.Time expiration_time@1;
+
+  // Contains extra information regarding the user's currently registered
+  // services.
+  string id_token@2;
+};
+
+[Stable]
+union AccessTokenResult {
+  AccessTokenInfo access_token_info;
+  GoogleServiceAuthError error;
+};
+
 // Interface for observers of Chrome OS Account Manager.
 // This interface is implemented by lacros-chrome, and is used by ash-chrome to
 // send account update notifications.
@@ -125,8 +148,8 @@
 // ARC++ uses components/arc/mojom/auth.mojom to talk to the Chrome OS Account
 // Manager.
 //
-// Next version: 6
-// Next method id: 7
+// Next version: 8
+// Next method id: 9
 [Stable, Uuid="85b9a674-9d8e-497f-98d5-22c8dca6f2b8"]
 interface AccountManager {
   // Returns |true| if Chrome OS Account Manager has been fully initialized, and
@@ -167,4 +190,28 @@
   [MinVersion = 5]
   GetPersistentErrorForAccount@6(AccountKey account) =>
       (GoogleServiceAuthError error);
+
+  // Returns a remote to an interface to fetch an access token for
+  // `account_key`, to be used by `oauth_consumer_name` OAuth2 client.
+  // `account_key` must be a Gaia account. If `account_key` is invalid or not
+  // known to Chrome OS Account Manager, fetching an access token via
+  // `AccessTokenFetcher` will result in an error (`kUserNotSignedUp`). If the
+  // account's refresh token is no longer valid, it will result in
+  // `kInvalidGaiaCredentials` error. See `GoogleServiceAuthError` for all error
+  // types.
+  [MinVersion = 6]
+  CreateAccessTokenFetcher@7(AccountKey account_key,
+      string oauth_consumer_name) =>
+          (pending_remote<AccessTokenFetcher> access_token_fetcher);
+};
+
+// Interface for fetching OAuth2 access tokens.
+[Stable, Uuid="71476f25-fb77-414f-848e-3e5368a9ee35"]
+interface AccessTokenFetcher {
+  // Starts the request for fetching an access token with the provided `scopes`.
+  // If `scopes` is empty, `access_token` will have the same scope as the
+  // underlying refresh token - login scope.
+  // Don't call this method multiple times - create a new `AccessTokenFetcher`
+  // instead.
+  Start@0(array<string> scopes) => (AccessTokenResult result);
 };
diff --git a/chromeos/dbus/hermes/fake_hermes_euicc_client.cc b/chromeos/dbus/hermes/fake_hermes_euicc_client.cc
index 9595f558..efaa12e3 100644
--- a/chromeos/dbus/hermes/fake_hermes_euicc_client.cc
+++ b/chromeos/dbus/hermes/fake_hermes_euicc_client.cc
@@ -188,6 +188,11 @@
   interactive_delay_ = delay;
 }
 
+std::string FakeHermesEuiccClient::GenerateFakeActivationCode() {
+  return base::StringPrintf("%s-%04d", kFakeActivationCodePrefix,
+                            fake_profile_counter_++);
+}
+
 void FakeHermesEuiccClient::InstallProfileFromActivationCode(
     const dbus::ObjectPath& euicc_path,
     const std::string& activation_code,
diff --git a/chromeos/dbus/hermes/fake_hermes_euicc_client.h b/chromeos/dbus/hermes/fake_hermes_euicc_client.h
index 4446684..6da7e55 100644
--- a/chromeos/dbus/hermes/fake_hermes_euicc_client.h
+++ b/chromeos/dbus/hermes/fake_hermes_euicc_client.h
@@ -58,6 +58,7 @@
                          bool service_only) override;
   void QueueHermesErrorStatus(HermesResponseStatus status) override;
   void SetInteractiveDelay(base::TimeDelta delay) override;
+  std::string GenerateFakeActivationCode() override;
 
   // HermesEuiccClient:
   void InstallProfileFromActivationCode(
diff --git a/chromeos/dbus/hermes/hermes_euicc_client.h b/chromeos/dbus/hermes/hermes_euicc_client.h
index 12439c6a..45ccc9f 100644
--- a/chromeos/dbus/hermes/hermes_euicc_client.h
+++ b/chromeos/dbus/hermes/hermes_euicc_client.h
@@ -65,6 +65,10 @@
 
     // Set delay for interactive methods.
     virtual void SetInteractiveDelay(base::TimeDelta delay) = 0;
+
+    // Returns a valid fake activation code that can be used to install
+    // a new fake carrier profile.
+    virtual std::string GenerateFakeActivationCode() = 0;
   };
 
   // Hermes Euicc properties.
diff --git a/chromeos/dbus/session_manager/session_manager_client.cc b/chromeos/dbus/session_manager/session_manager_client.cc
index b9c3bd4..68e1882 100644
--- a/chromeos/dbus/session_manager/session_manager_client.cc
+++ b/chromeos/dbus/session_manager/session_manager_client.cc
@@ -24,7 +24,6 @@
 #include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/writable_shared_memory_region.h"
 #include "base/metrics/histogram_functions.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/optional.h"
 #include "base/path_service.h"
 #include "base/strings/string_util.h"
@@ -78,30 +77,6 @@
   return RetrievePolicyResponseType::OTHER_ERROR;
 }
 
-// Logs UMA stat for retrieve policy request, corresponding to D-Bus method name
-// used.
-void LogPolicyResponseUma(login_manager::PolicyAccountType account_type,
-                          RetrievePolicyResponseType response) {
-  switch (account_type) {
-    case login_manager::ACCOUNT_TYPE_DEVICE:
-      UMA_HISTOGRAM_ENUMERATION("Enterprise.RetrievePolicyResponse.Device",
-                                response, RetrievePolicyResponseType::COUNT);
-      break;
-    case login_manager::ACCOUNT_TYPE_DEVICE_LOCAL_ACCOUNT:
-      UMA_HISTOGRAM_ENUMERATION(
-          "Enterprise.RetrievePolicyResponse.DeviceLocalAccount", response,
-          RetrievePolicyResponseType::COUNT);
-      break;
-    case login_manager::ACCOUNT_TYPE_USER:
-      UMA_HISTOGRAM_ENUMERATION("Enterprise.RetrievePolicyResponse.User",
-                                response, RetrievePolicyResponseType::COUNT);
-      break;
-    default:
-      NOTREACHED();
-      break;
-  }
-}
-
 // Creates a PolicyDescriptor object to store/retrieve Chrome policy.
 login_manager::PolicyDescriptor MakeChromePolicyDescriptor(
     login_manager::PolicyAccountType account_type,
@@ -779,7 +754,6 @@
     } else {
       policy_out->clear();
     }
-    LogPolicyResponseUma(descriptor.account_type(), result);
     return result;
   }
 
@@ -929,7 +903,6 @@
     if (!response) {
       RetrievePolicyResponseType response_type =
           GetPolicyResponseTypeByError(error ? error->GetErrorName() : "");
-      LogPolicyResponseUma(account_type, response_type);
       std::move(callback).Run(response_type, std::string());
       return;
     }
@@ -937,7 +910,6 @@
     dbus::MessageReader reader(response);
     std::string proto_blob;
     ExtractPolicyResponseString(account_type, response, &proto_blob);
-    LogPolicyResponseUma(account_type, RetrievePolicyResponseType::SUCCESS);
     std::move(callback).Run(RetrievePolicyResponseType::SUCCESS, proto_blob);
   }
 
diff --git a/chromeos/network/network_handler.cc b/chromeos/network/network_handler.cc
index 246c4c4c..a63a260f 100644
--- a/chromeos/network/network_handler.cc
+++ b/chromeos/network/network_handler.cc
@@ -183,6 +183,11 @@
   return auto_connect_handler_.get();
 }
 
+CellularESimConnectionHandler*
+NetworkHandler::cellular_esim_connection_handler() {
+  return cellular_esim_connection_handler_.get();
+}
+
 CellularESimProfileHandler* NetworkHandler::cellular_esim_profile_handler() {
   return cellular_esim_profile_handler_.get();
 }
diff --git a/chromeos/network/network_handler.h b/chromeos/network/network_handler.h
index 45411ac..35951e1c 100644
--- a/chromeos/network/network_handler.h
+++ b/chromeos/network/network_handler.h
@@ -80,6 +80,7 @@
   // explicit so that classes can be constructed explicitly in tests without
   // NetworkHandler.
   AutoConnectHandler* auto_connect_handler();
+  CellularESimConnectionHandler* cellular_esim_connection_handler();
   CellularESimProfileHandler* cellular_esim_profile_handler();
   CellularESimUninstallHandler* cellular_esim_uninstall_handler();
   CellularInhibitor* cellular_inhibitor();
diff --git a/chromeos/services/assistant/audio_decoder/ipc_data_source.cc b/chromeos/services/assistant/audio_decoder/ipc_data_source.cc
index ce474681..acde137 100644
--- a/chromeos/services/assistant/audio_decoder/ipc_data_source.cc
+++ b/chromeos/services/assistant/audio_decoder/ipc_data_source.cc
@@ -43,7 +43,7 @@
   utility_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&IPCDataSource::ReadMediaData, base::Unretained(this),
-                     destination, base::Passed(std::move(callback)), size));
+                     destination, std::move(callback), size));
 }
 
 bool IPCDataSource::GetSize(int64_t* size_out) {
@@ -68,9 +68,8 @@
   CHECK_GE(size, 0);
 
   media_data_source_->Read(
-      size,
-      base::BindOnce(&IPCDataSource::ReadDone, base::Unretained(this),
-                     destination, base::Passed(std::move(callback)), size));
+      size, base::BindOnce(&IPCDataSource::ReadDone, base::Unretained(this),
+                           destination, std::move(callback), size));
 }
 
 void IPCDataSource::ReadDone(uint8_t* destination,
diff --git a/chromeos/services/cellular_setup/esim_manager.cc b/chromeos/services/cellular_setup/esim_manager.cc
index 595c146..e078b63 100644
--- a/chromeos/services/cellular_setup/esim_manager.cc
+++ b/chromeos/services/cellular_setup/esim_manager.cc
@@ -39,17 +39,23 @@
 }  // namespace
 
 ESimManager::ESimManager()
-    : ESimManager(NetworkHandler::Get()->cellular_esim_profile_handler(),
+    : ESimManager(NetworkHandler::Get()->cellular_esim_connection_handler(),
+                  NetworkHandler::Get()->cellular_esim_profile_handler(),
                   NetworkHandler::Get()->cellular_esim_uninstall_handler(),
-                  NetworkHandler::Get()->cellular_inhibitor()) {}
+                  NetworkHandler::Get()->cellular_inhibitor(),
+                  NetworkHandler::Get()->network_connection_handler()) {}
 
 ESimManager::ESimManager(
+    CellularESimConnectionHandler* cellular_esim_connection_handler,
     CellularESimProfileHandler* cellular_esim_profile_handler,
     CellularESimUninstallHandler* cellular_esim_uninstall_handler,
-    CellularInhibitor* cellular_inhibitor)
-    : cellular_esim_profile_handler_(cellular_esim_profile_handler),
+    CellularInhibitor* cellular_inhibitor,
+    NetworkConnectionHandler* network_connection_handler)
+    : cellular_esim_connection_handler_(cellular_esim_connection_handler),
+      cellular_esim_profile_handler_(cellular_esim_profile_handler),
       cellular_esim_uninstall_handler_(cellular_esim_uninstall_handler),
-      cellular_inhibitor_(cellular_inhibitor) {
+      cellular_inhibitor_(cellular_inhibitor),
+      network_connection_handler_(network_connection_handler) {
   HermesManagerClient::Get()->AddObserver(this);
   HermesEuiccClient::Get()->AddObserver(this);
   cellular_esim_profile_handler_->AddObserver(this);
diff --git a/chromeos/services/cellular_setup/esim_manager.h b/chromeos/services/cellular_setup/esim_manager.h
index 3c2e7d3..11e64064 100644
--- a/chromeos/services/cellular_setup/esim_manager.h
+++ b/chromeos/services/cellular_setup/esim_manager.h
@@ -21,8 +21,10 @@
 
 namespace chromeos {
 
+class CellularESimConnectionHandler;
 class CellularESimUninstallHandler;
 class CellularInhibitor;
+class NetworkConnectionHandler;
 
 namespace cellular_setup {
 
@@ -39,9 +41,11 @@
                     HermesEuiccClient::Observer {
  public:
   ESimManager();
-  ESimManager(CellularESimProfileHandler* cellular_esim_profile_handler,
+  ESimManager(CellularESimConnectionHandler* cellular_esim_connection_handler,
+              CellularESimProfileHandler* cellular_esim_profile_handler,
               CellularESimUninstallHandler* cellular_esim_uninstall_handler,
-              CellularInhibitor* cellular_inhibitor);
+              CellularInhibitor* cellular_inhibitor,
+              NetworkConnectionHandler* network_connection_handler);
   ESimManager(const ESimManager&) = delete;
   ESimManager& operator=(const ESimManager&) = delete;
   ~ESimManager() override;
@@ -70,12 +74,20 @@
   // Notifies observers of changes to ESimProfile Lists.
   void NotifyESimProfileListChanged(Euicc* euicc);
 
+  CellularESimConnectionHandler* cellular_esim_connection_handler() {
+    return cellular_esim_connection_handler_;
+  }
+
   CellularESimUninstallHandler* cellular_esim_uninstall_handler() {
     return cellular_esim_uninstall_handler_;
   }
 
   CellularInhibitor* cellular_inhibitor() { return cellular_inhibitor_; }
 
+  NetworkConnectionHandler* network_connection_handler() {
+    return network_connection_handler_;
+  }
+
  private:
   void UpdateAvailableEuiccs();
   // Removes Euicc objects in |available_euiiccs_| that are not in
@@ -86,9 +98,13 @@
   // exist. Returns true if a new object was created.
   bool CreateEuiccIfNew(const dbus::ObjectPath& euicc_path);
 
+  CellularESimConnectionHandler* cellular_esim_connection_handler_;
   CellularESimProfileHandler* cellular_esim_profile_handler_;
   CellularESimUninstallHandler* cellular_esim_uninstall_handler_;
   CellularInhibitor* cellular_inhibitor_;
+
+  NetworkConnectionHandler* network_connection_handler_;
+
   std::vector<std::unique_ptr<Euicc>> available_euiccs_;
   mojo::RemoteSet<mojom::ESimManagerObserver> observers_;
   mojo::ReceiverSet<mojom::ESimManager> receivers_;
diff --git a/chromeos/services/cellular_setup/esim_test_base.cc b/chromeos/services/cellular_setup/esim_test_base.cc
index 30dee0d..d245214 100644
--- a/chromeos/services/cellular_setup/esim_test_base.cc
+++ b/chromeos/services/cellular_setup/esim_test_base.cc
@@ -11,6 +11,7 @@
 #include "chromeos/dbus/hermes/hermes_manager_client.h"
 #include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_manager_client.h"
+#include "chromeos/network/cellular_esim_connection_handler.h"
 #include "chromeos/network/cellular_esim_uninstall_handler.h"
 #include "chromeos/network/cellular_inhibitor.h"
 #include "chromeos/network/fake_network_connection_handler.h"
@@ -53,6 +54,10 @@
   cellular_inhibitor_ = std::make_unique<CellularInhibitor>();
   cellular_inhibitor_->Init(network_state_handler_.get(),
                             network_device_handler_.get());
+  cellular_esim_connection_handler_ =
+      std::make_unique<CellularESimConnectionHandler>();
+  cellular_esim_connection_handler_->Init(network_state_handler_.get(),
+                                          cellular_inhibitor_.get());
   cellular_esim_uninstall_handler_ =
       std::make_unique<CellularESimUninstallHandler>();
   cellular_esim_uninstall_handler_->Init(
@@ -63,8 +68,10 @@
   cellular_esim_profile_handler_->Init();
 
   esim_manager_ = std::make_unique<ESimManager>(
+      cellular_esim_connection_handler_.get(),
       cellular_esim_profile_handler_.get(),
-      cellular_esim_uninstall_handler_.get(), cellular_inhibitor_.get());
+      cellular_esim_uninstall_handler_.get(), cellular_inhibitor_.get(),
+      network_connection_handler_.get());
   observer_ = std::make_unique<ESimManagerTestObserver>();
   esim_manager_->AddObserver(observer_->GenerateRemote());
 }
diff --git a/chromeos/services/cellular_setup/esim_test_base.h b/chromeos/services/cellular_setup/esim_test_base.h
index 04a3a61..96ac2935 100644
--- a/chromeos/services/cellular_setup/esim_test_base.h
+++ b/chromeos/services/cellular_setup/esim_test_base.h
@@ -24,6 +24,7 @@
 class NetworkDeviceHandler;
 class NetworkConfigurationHandler;
 class FakeNetworkConnectionHandler;
+class CellularESimConnectionHandler;
 class CellularESimUninstallHandler;
 class CellularESimProfileHandler;
 
@@ -57,6 +58,14 @@
   ESimManager* esim_manager() { return esim_manager_.get(); }
   ESimManagerTestObserver* observer() { return observer_.get(); }
 
+  FakeNetworkConnectionHandler* network_connection_handler() {
+    return network_connection_handler_.get();
+  }
+
+  NetworkStateHandler* network_state_handler() {
+    return network_state_handler_.get();
+  }
+
  private:
   std::unique_ptr<CellularInhibitor> cellular_inhibitor_;
   std::unique_ptr<NetworkStateHandler> network_state_handler_;
@@ -66,6 +75,8 @@
   std::unique_ptr<CellularESimUninstallHandler>
       cellular_esim_uninstall_handler_;
   std::unique_ptr<CellularESimProfileHandler> cellular_esim_profile_handler_;
+  std::unique_ptr<CellularESimConnectionHandler>
+      cellular_esim_connection_handler_;
 
   base::test::SingleThreadTaskEnvironment task_environment_;
   std::unique_ptr<ESimManager> esim_manager_;
diff --git a/chromeos/services/cellular_setup/euicc.cc b/chromeos/services/cellular_setup/euicc.cc
index 333b2e7..2abf100 100644
--- a/chromeos/services/cellular_setup/euicc.cc
+++ b/chromeos/services/cellular_setup/euicc.cc
@@ -9,8 +9,10 @@
 
 #include "base/optional.h"
 #include "base/strings/strcat.h"
+#include "chromeos/network/cellular_esim_connection_handler.h"
 #include "chromeos/network/cellular_esim_profile.h"
 #include "chromeos/network/cellular_inhibitor.h"
+#include "chromeos/network/network_connection_handler.h"
 #include "chromeos/network/network_event_log.h"
 #include "chromeos/services/cellular_setup/esim_manager.h"
 #include "chromeos/services/cellular_setup/esim_mojo_utils.h"
@@ -83,6 +85,9 @@
   }
 
   // Try installing directly with activation code.
+  // TODO(crbug.com/1186682) Add a check for activation codes that are
+  // currently being installed to prevent multiple attempts for the same
+  // activation code.
   NET_LOG(USER) << "Attempting installation with code " << activation_code;
   esim_manager_->cellular_inhibitor()->InhibitCellularScanning(
       base::BindOnce(&Euicc::PerformInstallProfileFromActivationCode,
@@ -204,7 +209,7 @@
     InstallProfileFromActivationCodeCallback callback,
     std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock,
     HermesResponseStatus status,
-    const dbus::ObjectPath* object_path) {
+    const dbus::ObjectPath* profile_path) {
   if (status != HermesResponseStatus::kSuccess) {
     NET_LOG(ERROR) << "Error Installing profile status="
                    << static_cast<int>(status);
@@ -213,18 +218,67 @@
     return;
   }
 
-  ESimProfile* esim_profile = GetProfileFromPath(*object_path);
+  install_calls_pending_connect_.emplace(*profile_path, std::move(callback));
+  esim_manager_->cellular_esim_connection_handler()
+      ->EnableNewProfileForConnection(
+          path_, *profile_path, std::move(inhibit_lock),
+          base::BindOnce(&Euicc::OnNewProfileEnableSuccess,
+                         weak_ptr_factory_.GetWeakPtr(), *profile_path),
+          base::BindOnce(&Euicc::OnNewProfileConnectFailure,
+                         weak_ptr_factory_.GetWeakPtr(), *profile_path));
+}
+
+void Euicc::OnNewProfileEnableSuccess(const dbus::ObjectPath& profile_path,
+                                      const std::string& service_path) {
+  esim_manager_->network_connection_handler()->ConnectToNetwork(
+      service_path,
+      base::BindOnce(&Euicc::OnNewProfileConnectSuccess,
+                     weak_ptr_factory_.GetWeakPtr(), profile_path),
+      base::BindOnce(&Euicc::OnNewProfileConnectFailure,
+                     weak_ptr_factory_.GetWeakPtr(), profile_path),
+      /*check_error_state=*/false, ConnectCallbackMode::ON_COMPLETED);
+}
+
+void Euicc::OnNewProfileConnectSuccess(const dbus::ObjectPath& profile_path) {
+  auto it = install_calls_pending_connect_.find(profile_path);
+  if (it == install_calls_pending_connect_.end()) {
+    NET_LOG(ERROR) << "ESim profile install callback missing after connect "
+                      "success. profile_path="
+                   << profile_path.value();
+    return;
+  }
+  InstallProfileFromActivationCodeCallback callback = std::move(it->second);
+  install_calls_pending_connect_.erase(it);
+
+  ESimProfile* esim_profile = GetProfileFromPath(profile_path);
   if (!esim_profile) {
     // An ESimProfile may not exist for the newly created esim profile object
     // path if ESimProfileHandler has not updated profile lists yet. Save the
     // callback until an UpdateProfileList call creates an ESimProfile
     // object for this path
-    install_calls_pending_create_.emplace(*object_path, std::move(callback));
+    install_calls_pending_create_.emplace(profile_path, std::move(callback));
     return;
   }
   std::move(callback).Run(mojom::ProfileInstallResult::kSuccess,
                           esim_profile->CreateRemote());
-  // inhibit_lock goes out of scope and will uninhibit automatically.
+}
+
+void Euicc::OnNewProfileConnectFailure(
+    const dbus::ObjectPath& profile_path,
+    const std::string& error_name,
+    std::unique_ptr<base::DictionaryValue> error_data) {
+  NET_LOG(ERROR) << "Error connecting to newly created profile path="
+                 << profile_path.value() << " error_name=" << error_name;
+
+  auto it = install_calls_pending_connect_.find(profile_path);
+  if (it == install_calls_pending_connect_.end()) {
+    NET_LOG(ERROR)
+        << "ESim profile install callback missing after connect failure";
+    return;
+  }
+  std::move(it->second)
+      .Run(mojom::ProfileInstallResult::kFailure, mojo::NullRemote());
+  install_calls_pending_connect_.erase(it);
 }
 
 void Euicc::PerformRequestPendingProfiles(
diff --git a/chromeos/services/cellular_setup/euicc.h b/chromeos/services/cellular_setup/euicc.h
index c429774b..250b1c0 100644
--- a/chromeos/services/cellular_setup/euicc.h
+++ b/chromeos/services/cellular_setup/euicc.h
@@ -5,6 +5,7 @@
 #ifndef CHROMEOS_SERVICES_CELLULAR_SETUP_EUICC_H_
 #define CHROMEOS_SERVICES_CELLULAR_SETUP_EUICC_H_
 
+#include "base/values.h"
 #include "chromeos/dbus/hermes/hermes_euicc_client.h"
 #include "chromeos/dbus/hermes/hermes_profile_client.h"
 #include "chromeos/network/cellular_inhibitor.h"
@@ -75,6 +76,13 @@
       std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock,
       HermesResponseStatus status,
       const dbus::ObjectPath* object_path);
+  void OnNewProfileEnableSuccess(const dbus::ObjectPath& profile_path,
+                                 const std::string& service_path);
+  void OnNewProfileConnectSuccess(const dbus::ObjectPath& profile_path);
+  void OnNewProfileConnectFailure(
+      const dbus::ObjectPath& profile_path,
+      const std::string& error_name,
+      std::unique_ptr<base::DictionaryValue> error_data);
   void PerformRequestPendingProfiles(
       RequestPendingProfilesCallback callback,
       std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock);
@@ -101,10 +109,15 @@
   mojom::EuiccPropertiesPtr properties_;
   dbus::ObjectPath path_;
   std::vector<std::unique_ptr<ESimProfile>> esim_profiles_;
+
   // Maps profile dbus paths to InstallProfileFromActivation method callbacks
   // that are pending creation of a new ESimProfile object.
   std::map<dbus::ObjectPath, InstallProfileFromActivationCodeCallback>
       install_calls_pending_create_;
+  // Maps profile dbus paths to InstallProfileFromActivation method callbacks
+  // that are pending connection to the newly created network.
+  std::map<dbus::ObjectPath, InstallProfileFromActivationCodeCallback>
+      install_calls_pending_connect_;
 
   base::WeakPtrFactory<Euicc> weak_ptr_factory_{this};
 };
diff --git a/chromeos/services/cellular_setup/euicc_unittest.cc b/chromeos/services/cellular_setup/euicc_unittest.cc
index 36190c39..ddd3a46 100644
--- a/chromeos/services/cellular_setup/euicc_unittest.cc
+++ b/chromeos/services/cellular_setup/euicc_unittest.cc
@@ -4,11 +4,15 @@
 
 #include <utility>
 
+#include "base/run_loop.h"
 #include "chromeos/dbus/hermes/hermes_euicc_client.h"
 #include "chromeos/dbus/hermes/hermes_profile_client.h"
+#include "chromeos/network/fake_network_connection_handler.h"
+#include "chromeos/network/network_type_pattern.h"
 #include "chromeos/services/cellular_setup/esim_test_base.h"
 #include "chromeos/services/cellular_setup/esim_test_utils.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
+#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
 
 namespace chromeos {
 namespace cellular_setup {
@@ -18,31 +22,6 @@
 using InstallResultPair = std::pair<mojom::ProfileInstallResult,
                                     mojo::PendingRemote<mojom::ESimProfile>>;
 
-InstallResultPair InstallProfileFromActivationCode(
-    const mojo::Remote<mojom::Euicc>& euicc,
-    const std::string& activation_code,
-    const std::string& confirmation_code) {
-  mojom::ProfileInstallResult install_result;
-  mojo::PendingRemote<mojom::ESimProfile> esim_profile;
-
-  base::RunLoop run_loop;
-  euicc->InstallProfileFromActivationCode(
-      activation_code, confirmation_code,
-      base::BindOnce(
-          [](mojom::ProfileInstallResult* out_install_result,
-             mojo::PendingRemote<mojom::ESimProfile>* out_esim_profile,
-             base::OnceClosure quit_closure,
-             mojom::ProfileInstallResult install_result,
-             mojo::PendingRemote<mojom::ESimProfile> esim_profile) {
-            *out_install_result = install_result;
-            *out_esim_profile = std::move(esim_profile);
-            std::move(quit_closure).Run();
-          },
-          &install_result, &esim_profile, run_loop.QuitClosure()));
-  run_loop.Run();
-  return std::make_pair(install_result, std::move(esim_profile));
-}
-
 mojom::ESimOperationResult RequestPendingProfiles(
     mojo::Remote<mojom::Euicc>& euicc) {
   mojom::ESimOperationResult result;
@@ -70,6 +49,50 @@
     ESimTestBase::SetUp();
     SetupEuicc();
   }
+
+  InstallResultPair InstallProfileFromActivationCode(
+      const mojo::Remote<mojom::Euicc>& euicc,
+      const std::string& activation_code,
+      const std::string& confirmation_code,
+      bool wait_for_connect,
+      bool fail_connect) {
+    mojom::ProfileInstallResult install_result;
+    mojo::PendingRemote<mojom::ESimProfile> esim_profile;
+
+    base::RunLoop run_loop;
+    euicc->InstallProfileFromActivationCode(
+        activation_code, confirmation_code,
+        base::BindOnce(
+            [](mojom::ProfileInstallResult* out_install_result,
+               mojo::PendingRemote<mojom::ESimProfile>* out_esim_profile,
+               base::OnceClosure quit_closure,
+               mojom::ProfileInstallResult install_result,
+               mojo::PendingRemote<mojom::ESimProfile> esim_profile) {
+              *out_install_result = install_result;
+              *out_esim_profile = std::move(esim_profile);
+              std::move(quit_closure).Run();
+            },
+            &install_result, &esim_profile, run_loop.QuitClosure()));
+
+    if (wait_for_connect) {
+      base::RunLoop().RunUntilIdle();
+      EXPECT_LE(1u, network_connection_handler()->connect_calls().size());
+      if (fail_connect) {
+        network_connection_handler()
+            ->connect_calls()
+            .back()
+            .InvokeErrorCallback("fake_error_name", /*error_data=*/nullptr);
+      } else {
+        network_connection_handler()
+            ->connect_calls()
+            .back()
+            .InvokeSuccessCallback();
+      }
+    }
+
+    run_loop.Run();
+    return std::make_pair(install_result, std::move(esim_profile));
+  }
 };
 
 TEST_F(EuiccTest, GetProperties) {
@@ -110,22 +133,49 @@
   // Verify that install errors return error code properly.
   euicc_test->QueueHermesErrorStatus(
       HermesResponseStatus::kErrorInvalidActivationCode);
-  InstallResultPair result_pair =
-      InstallProfileFromActivationCode(euicc, "", "");
+  InstallResultPair result_pair = InstallProfileFromActivationCode(
+      euicc, /*activation_code=*/std::string(),
+      /*confirmation_code=*/std::string(), /*wait_for_connect=*/false,
+      /*fail_connect=*/false);
   EXPECT_EQ(mojom::ProfileInstallResult::kErrorInvalidActivationCode,
             result_pair.first);
   EXPECT_FALSE(result_pair.second.is_valid());
 
-  // Verify that installing a profile returns proper status code
-  // and profile object.
+  // Verify that connect failures are handled properly.
+  result_pair = InstallProfileFromActivationCode(
+      euicc, euicc_test->GenerateFakeActivationCode(),
+      /*confirmation_code=*/std::string(), /*wait_for_connect=*/true,
+      /*fail_connect=*/true);
+  EXPECT_EQ(mojom::ProfileInstallResult::kFailure, result_pair.first);
+  ASSERT_FALSE(result_pair.second.is_valid());
+
+  // Verify that install succeeds when valid activation code is passed.
+  result_pair = InstallProfileFromActivationCode(
+      euicc, euicc_test->GenerateFakeActivationCode(),
+      /*confirmation_code=*/std::string(), /*wait_for_connect=*/true,
+      /*fail_connect=*/false);
+  EXPECT_EQ(mojom::ProfileInstallResult::kSuccess, result_pair.first);
+  ASSERT_TRUE(result_pair.second.is_valid());
+}
+
+TEST_F(EuiccTest, InstallPendingProfileFromActivationCode) {
+  mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid);
+  ASSERT_TRUE(euicc.is_bound());
+
+  HermesEuiccClient::TestInterface* euicc_test =
+      HermesEuiccClient::Get()->GetTestInterface();
+  // Verify that installing a pending profile with it's activation code returns
+  // proper status code and profile object.
   dbus::ObjectPath profile_path = euicc_test->AddFakeCarrierProfile(
       dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kPending, "",
       /*service_only=*/false);
   base::RunLoop().RunUntilIdle();
   HermesProfileClient::Properties* dbus_properties =
       HermesProfileClient::Get()->GetProperties(profile_path);
-  result_pair = InstallProfileFromActivationCode(
-      euicc, dbus_properties->activation_code().value(), "");
+  InstallResultPair result_pair = InstallProfileFromActivationCode(
+      euicc, dbus_properties->activation_code().value(),
+      /*confirmation_code=*/std::string(), /*wait_for_connect=*/false,
+      /*fail_connect=*/false);
   EXPECT_EQ(mojom::ProfileInstallResult::kSuccess, result_pair.first);
   ASSERT_TRUE(result_pair.second.is_valid());
 
diff --git a/chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom b/chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom
index 0432282..1a65a45 100644
--- a/chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom
+++ b/chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Next MinVersion: 1
+
 // Top-level API of the Machine Learning Service: loading models for inference.
 
 // NOTE: This mojom exists in two places and must be kept in sync:
@@ -37,7 +39,7 @@
 
 // Top-level interface between Chromium and the ML Service daemon.
 // Next ordinal: 8
-[Stable]
+[Stable, Uuid="9e5e4750-40cc-4eda-ac09-3457d06a45ab"]
 interface MachineLearningService {
   // Binds another pipe to this instance.
   Clone@5(pending_receiver<MachineLearningService> receiver);
diff --git a/chromeos/strings/BUILD.gn b/chromeos/strings/BUILD.gn
index 9ef9638e..3dabf49 100644
--- a/chromeos/strings/BUILD.gn
+++ b/chromeos/strings/BUILD.gn
@@ -10,7 +10,7 @@
   source = "../chromeos_strings.grd"
   outputs =
       [ "grit/chromeos_strings.h" ] +
-      process_file_template(locales_with_fake_bidi,
+      process_file_template(locales_with_pseudolocales,
                             [ "chromeos_strings_{{source_name_part}}.pak" ])
 }
 
diff --git a/chromeos/tast_control.gni b/chromeos/tast_control.gni
index ffac02a3..76546cc 100644
--- a/chromeos/tast_control.gni
+++ b/chromeos/tast_control.gni
@@ -52,4 +52,10 @@
 
   # crbug.com/1184931
   "health.ProbeBatteryMetrics",
+
+  # crbug.com/1185030
+  "inputs.VirtualKeyboardJapaneseInputs.stable",
+
+  # crbug.com/1185031
+  "inputs.VirtualKeyboardInputFields.jp_us_stable",
 ]
diff --git a/components/BUILD.gn b/components/BUILD.gn
index aebe412..6164dfb 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -282,6 +282,7 @@
       "//components/page_image_annotation/content/renderer:unit_tests",
       "//components/page_image_annotation/core:unit_tests",
       "//components/page_load_metrics/browser:unit_tests",
+      "//components/page_load_metrics/browser/observers/ad_metrics:unit_tests",
       "//components/page_load_metrics/renderer:unit_tests",
       "//components/paint_preview/browser:unit_tests",
       "//components/paint_preview/common:unit_tests",
@@ -368,6 +369,7 @@
       "//components/autofill_assistant/browser:unit_tests",
       "//components/browser_ui/sms/android:unit_tests",
       "//components/cdm/browser:unit_tests",
+      "//components/component_updater/android:embedded_component_loader_java",
       "//components/component_updater/android:embedded_component_loader_unittests",
       "//components/crash/android:java",
       "//components/crash/android:unit_tests",
diff --git a/components/account_manager_core/account_manager_facade_impl_unittest.cc b/components/account_manager_core/account_manager_facade_impl_unittest.cc
index b1e6fcc..11411c8 100644
--- a/components/account_manager_core/account_manager_facade_impl_unittest.cc
+++ b/components/account_manager_core/account_manager_facade_impl_unittest.cc
@@ -17,6 +17,7 @@
 #include "components/account_manager_core/account_manager_facade.h"
 #include "components/account_manager_core/account_manager_test_util.h"
 #include "components/account_manager_core/account_manager_util.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -89,6 +90,14 @@
     show_manage_accounts_settings_calls_++;
   }
 
+  void CreateAccessTokenFetcher(
+      crosapi::mojom::AccountKeyPtr mojo_account_key,
+      const std::string& oauth_consumer_name,
+      CreateAccessTokenFetcherCallback callback) override {
+    mojo::PendingRemote<crosapi::mojom::AccessTokenFetcher> pending_remote;
+    std::move(callback).Run(std::move(pending_remote));
+  }
+
   mojo::Remote<crosapi::mojom::AccountManager> CreateRemote() {
     mojo::Remote<crosapi::mojom::AccountManager> remote;
     receivers_.Add(this, remote.BindNewPipeAndPassReceiver());
diff --git a/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java b/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java
index db37521..237cf24 100644
--- a/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java
+++ b/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java
@@ -76,7 +76,17 @@
         }
 
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-            ComponentName componentName = mAutofillManager.getAutofillServiceComponentName();
+            ComponentName componentName = null;
+            try {
+                componentName = mAutofillManager.getAutofillServiceComponentName();
+            } catch (Exception e) {
+                // Can't catch com.android.internal.util.SyncResultReceiver.TimeoutException,
+                // because
+                // - The exception isn't Android API.
+                // - Different version of Android handle it differently.
+                // Uses Exception to catch various cases. (refer to crbug.com/1186406)
+                Log.e(TAG, "getAutofillServiceComponentName", e);
+            }
             if (componentName != null) {
                 mIsAwGCurrentAutofillService =
                         AWG_COMPONENT_NAME.equals(componentName.flattenToString());
diff --git a/components/autofill/core/common/autofill_payments_features.cc b/components/autofill/core/common/autofill_payments_features.cc
index 1bcfa67e..6170457 100644
--- a/components/autofill/core/common/autofill_payments_features.cc
+++ b/components/autofill/core/common/autofill_payments_features.cc
@@ -73,7 +73,7 @@
 // When enabled, offer data will be retrieved during downstream and shown in
 // the dropdown list.
 const base::Feature kAutofillEnableOffersInDownstream{
-    "kAutofillEnableOffersInDownstream", base::FEATURE_DISABLED_BY_DEFAULT};
+    "kAutofillEnableOffersInDownstream", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // When enabled and user is signed in, a footer indicating user's e-mail address
 // and profile picture will appear at the bottom of SaveCardInfoBar.
diff --git a/components/autofill/ios/form_util/form_activity_params.h b/components/autofill/ios/form_util/form_activity_params.h
index 0feafa8..6639ecc 100644
--- a/components/autofill/ios/form_util/form_activity_params.h
+++ b/components/autofill/ios/form_util/form_activity_params.h
@@ -10,6 +10,9 @@
 
 namespace autofill {
 
+// HTML password field type.
+constexpr char kPasswordFieldType[] = "password";
+
 // Wraps information about event happening in a form.
 // Example HTML
 // <form name="np" id="np1" action="https://example.com/" method="post">
diff --git a/components/browser_ui/notifications/android/java/src/org/chromium/components/browser_ui/notifications/NotificationWrapperCompatBuilder.java b/components/browser_ui/notifications/android/java/src/org/chromium/components/browser_ui/notifications/NotificationWrapperCompatBuilder.java
index dbf0be9..d2c7389 100644
--- a/components/browser_ui/notifications/android/java/src/org/chromium/components/browser_ui/notifications/NotificationWrapperCompatBuilder.java
+++ b/components/browser_ui/notifications/android/java/src/org/chromium/components/browser_ui/notifications/NotificationWrapperCompatBuilder.java
@@ -19,6 +19,7 @@
 import androidx.core.graphics.drawable.IconCompat;
 
 import org.chromium.base.Log;
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.components.browser_ui.notifications.channels.ChannelsInitializer;
 
 /**
@@ -332,12 +333,16 @@
 
     @Override
     public Notification build() {
+        boolean success = false;
         Notification notification = null;
         try {
             notification = mBuilder.build();
+            success = true;
         } catch (NullPointerException e) {
             // Android M and L may throw exception, see https://crbug.com/949794.
             Log.e(TAG, "Failed to build notification.", e);
+        } finally {
+            RecordHistogram.recordBooleanHistogram("Notifications.Android.Build", success);
         }
         return notification;
     }
diff --git a/components/browser_ui/notifications/android/java/src/org/chromium/components/browser_ui/notifications/NotificationWrapperStandardBuilder.java b/components/browser_ui/notifications/android/java/src/org/chromium/components/browser_ui/notifications/NotificationWrapperStandardBuilder.java
index b7e7f51..d0c7a1d 100644
--- a/components/browser_ui/notifications/android/java/src/org/chromium/components/browser_ui/notifications/NotificationWrapperStandardBuilder.java
+++ b/components/browser_ui/notifications/android/java/src/org/chromium/components/browser_ui/notifications/NotificationWrapperStandardBuilder.java
@@ -19,6 +19,7 @@
 import androidx.core.app.NotificationCompat;
 
 import org.chromium.base.Log;
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.components.browser_ui.notifications.channels.ChannelsInitializer;
 
 /**
@@ -354,7 +355,14 @@
 
     @Override
     public Notification build() {
-        return mBuilder.build();
+        boolean success = false;
+        try {
+            Notification notification = mBuilder.build();
+            success = true;
+            return notification;
+        } finally {
+            RecordHistogram.recordBooleanHistogram("Notifications.Android.Build", success);
+        }
     }
 
     @Override
diff --git a/components/component_updater/android/BUILD.gn b/components/component_updater/android/BUILD.gn
index 979bac1..de9747bc 100644
--- a/components/component_updater/android/BUILD.gn
+++ b/components/component_updater/android/BUILD.gn
@@ -48,12 +48,31 @@
   srcjar_deps = [ ":component_provider_service_aidl" ]
 }
 
-static_library("embedded_component_loader") {
+android_library("embedded_component_loader_java") {
   sources = [
-    "embedded_component_loader.cc",
-    "embedded_component_loader.h",
+    "java/src/org/chromium/components/component_updater/ComponentLoaderPolicyBridge.java",
+    "java/src/org/chromium/components/component_updater/EmbeddedComponentLoader.java",
   ]
   deps = [
+    ":component_provider_service_aidl_java",
+    ":embedded_component_loader_jni_headers",
+    "//base:base_java",
+    "//base:jni_java",
+  ]
+  annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
+}
+
+generate_jni("embedded_component_loader_jni_headers") {
+  sources = [ "java/src/org/chromium/components/component_updater/ComponentLoaderPolicyBridge.java" ]
+}
+
+static_library("embedded_component_loader") {
+  sources = [
+    "component_loader_policy.cc",
+    "component_loader_policy.h",
+  ]
+  deps = [
+    ":embedded_component_loader_jni_headers",
     "//base",
     "//components/component_updater",
     "//components/update_client",
@@ -62,7 +81,7 @@
 
 source_set("embedded_component_loader_unittests") {
   testonly = true
-  sources = [ "embedded_component_loader_unittests.cc" ]
+  sources = [ "component_loader_policy_unittests.cc" ]
   deps = [
     ":embedded_component_loader",
     "//base",
@@ -71,3 +90,17 @@
     "//testing/gtest",
   ]
 }
+
+static_library("loader_policies") {
+  sources = [
+    "loader_policies/trust_token_key_commitments_component_loader_policy.cc",
+    "loader_policies/trust_token_key_commitments_component_loader_policy.h",
+  ]
+
+  deps = [
+    ":embedded_component_loader",
+    "//base",
+    "//components/component_updater",
+    "//components/component_updater/installer_policies",
+  ]
+}
diff --git a/components/component_updater/android/embedded_component_loader.cc b/components/component_updater/android/component_loader_policy.cc
similarity index 69%
rename from components/component_updater/android/embedded_component_loader.cc
rename to components/component_updater/android/component_loader_policy.cc
index 1be6536..e647649 100644
--- a/components/component_updater/android/embedded_component_loader.cc
+++ b/components/component_updater/android/component_loader_policy.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/component_updater/android/embedded_component_loader.h"
+#include "components/component_updater/android/component_loader_policy.h"
 
 #include <jni.h>
 #include <stdio.h>
@@ -14,6 +14,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/android/jni_android.h"
 #include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
 #include "base/bind.h"
@@ -26,13 +27,13 @@
 #include "base/json/json_string_value_serializer.h"
 #include "base/location.h"
 #include "base/memory/scoped_refptr.h"
-#include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "base/values.h"
 #include "base/version.h"
+#include "components/component_updater/android/embedded_component_loader_jni_headers/ComponentLoaderPolicyBridge_jni.h"
 #include "components/update_client/utils.h"
 
 namespace component_updater {
@@ -71,13 +72,24 @@
 
 ComponentLoaderPolicy::~ComponentLoaderPolicy() = default;
 
-EmbeddedComponentLoader::EmbeddedComponentLoader(
+AndroidComponentLoaderPolicy::AndroidComponentLoaderPolicy(
     std::unique_ptr<ComponentLoaderPolicy> loader_policy)
-    : loader_policy_(std::move(loader_policy)) {}
+    : loader_policy_(std::move(loader_policy)) {
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+  JNIEnv* env = base::android::AttachCurrentThread();
+  obj_.Reset(env, Java_ComponentLoaderPolicyBridge_Constructor(
+                      env, reinterpret_cast<intptr_t>(this))
+                      .obj());
+}
 
-EmbeddedComponentLoader::~EmbeddedComponentLoader() = default;
+AndroidComponentLoaderPolicy::~AndroidComponentLoaderPolicy() = default;
 
-void EmbeddedComponentLoader::ComponentLoaded(
+base::android::ScopedJavaLocalRef<jobject>
+AndroidComponentLoaderPolicy::GetJavaObject() {
+  return base::android::ScopedJavaLocalRef<jobject>(obj_);
+}
+
+void AndroidComponentLoaderPolicy::ComponentLoaded(
     JNIEnv* env,
     const base::android::JavaRef<jobjectArray>& jfile_names,
     const base::android::JavaRef<jintArray>& jfds) {
@@ -112,24 +124,25 @@
       {base::ThreadPool(), base::MayBlock(),
        base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
       base::BindOnce(ReadManifestFromFd, manifest_fd),
-      base::BindOnce(&EmbeddedComponentLoader::NotifyNewVersion,
-                     weak_ptr_factory_.GetWeakPtr(), fd_map));
+      base::BindOnce(&AndroidComponentLoaderPolicy::NotifyNewVersion,
+                     base::Owned(this), fd_map));
 }
 
-void EmbeddedComponentLoader::ComponentLoadFailed(JNIEnv* env) {
+void AndroidComponentLoaderPolicy::ComponentLoadFailed(JNIEnv* env) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   loader_policy_->ComponentLoadFailed();
+  delete this;
 }
 
 base::android::ScopedJavaLocalRef<jstring>
-EmbeddedComponentLoader::GetComponentId(JNIEnv* env) {
+AndroidComponentLoaderPolicy::GetComponentId(JNIEnv* env) {
   std::vector<uint8_t> hash;
   loader_policy_->GetHash(&hash);
   return base::android::ConvertUTF8ToJavaString(
       env, update_client::GetCrxIdFromPublicKeyHash(hash));
 }
 
-void EmbeddedComponentLoader::NotifyNewVersion(
+void AndroidComponentLoaderPolicy::NotifyNewVersion(
     const base::flat_map<std::string, int>& fd_map,
     std::unique_ptr<base::DictionaryValue> manifest) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -148,7 +161,7 @@
   loader_policy_->ComponentLoaded(version, fd_map, std::move(manifest));
 }
 
-void EmbeddedComponentLoader::CloseFdsAndFail(
+void AndroidComponentLoaderPolicy::CloseFdsAndFail(
     const base::flat_map<std::string, int>& fd_map) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   for (auto& iter : fd_map) {
@@ -157,4 +170,24 @@
   loader_policy_->ComponentLoadFailed();
 }
 
+// static
+base::android::ScopedJavaLocalRef<jobjectArray>
+AndroidComponentLoaderPolicy::ToJavaArrayOfAndroidComponentLoaderPolicy(
+    JNIEnv* env,
+    ComponentLoaderPolicyVector policies) {
+  base::android::ScopedJavaLocalRef<jobjectArray> policy_array =
+      Java_ComponentLoaderPolicyBridge_createNewArray(env, policies.size());
+
+  for (size_t i = 0; i < policies.size(); ++i) {
+    // The `AndroidComponentLoaderPolicy` object is owned by the java
+    // ComponentLoaderPolicy object which manages its life time and will triger
+    // deletion.
+    auto* android_policy =
+        new AndroidComponentLoaderPolicy(std::move(policies[i]));
+    Java_ComponentLoaderPolicyBridge_setArrayElement(
+        env, policy_array, i, android_policy->GetJavaObject());
+  }
+  return policy_array;
+}
+
 }  // namespace component_updater
diff --git a/components/component_updater/android/component_loader_policy.h b/components/component_updater/android/component_loader_policy.h
new file mode 100644
index 0000000..f897f50
--- /dev/null
+++ b/components/component_updater/android/component_loader_policy.h
@@ -0,0 +1,132 @@
+// 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 COMPONENTS_COMPONENT_UPDATER_ANDROID_COMPONENT_LOADER_POLICY_H_
+#define COMPONENTS_COMPONENT_UPDATER_ANDROID_COMPONENT_LOADER_POLICY_H_
+
+#include <jni.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/containers/flat_map.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+
+namespace base {
+class Version;
+class DictionaryValue;
+}  // namespace base
+
+namespace component_updater {
+
+// Components should use `AndroidComponentLoaderPolicy` by defining a class that
+// implements the members of `ComponentLoaderPolicy`, and then registering a
+// `AndroidComponentLoaderPolicy` that has been constructed with an instance of
+// that class in an instance of embedded WebView or WebLayer with the Java
+// AndroidComponentLoaderPolicy. The `AndroidComponentLoaderPolicy` will fetch
+// the components files from the Android `ComponentsProviderService` and invoke
+// the callbacks defined in this class.
+//
+// Ideally, the implementation of this class should share implementation with
+// its component `ComponentInstallerPolicy` counterpart.
+//
+// Can be used on a thread that is different from the thread it is created on.
+class ComponentLoaderPolicy {
+ public:
+  virtual ~ComponentLoaderPolicy();
+
+  // ComponentLoaded is called when the loader successfully gets file
+  // descriptors for all files in the component from the
+  // ComponentsProviderService.
+  //
+  // Will be called at most once. This is mutually exclusive with
+  // ComponentLoadFailed; if this is called then ComponentLoadFailed won't be
+  // called.
+  //
+  // Overriders must close all file descriptors after using them.
+  //
+  // `version` is the version of the component.
+  // `fd_map` maps file relative paths in the install directory to its file
+  //          descriptor.
+  // `manifest` is the manifest for this version of the component.
+  virtual void ComponentLoaded(
+      const base::Version& version,
+      const base::flat_map<std::string, int>& fd_map,
+      std::unique_ptr<base::DictionaryValue> manifest) = 0;
+
+  // Called if connection to the service fails, components files are not found
+  // or if the manifest file is missing or invalid.
+  //
+  // Will be called at most once. This is mutually exclusive with
+  // ComponentLoaded; if this is called then ComponentLoaded won't be called.
+  //
+  // TODO(crbug.com/1180966) accept error code for different types of errors.
+  virtual void ComponentLoadFailed() = 0;
+
+  // Returns the component's SHA2 hash as raw bytes, the hash value is used as
+  // the unique id of the component and will be used to request components files
+  // from the ComponentsProviderService.
+  virtual void GetHash(std::vector<uint8_t>* hash) const = 0;
+};
+
+using ComponentLoaderPolicyVector =
+    std::vector<std::unique_ptr<ComponentLoaderPolicy>>;
+
+// Provides a bridge from Java to native to receive callbacks from the Java
+// loader and pass it to the wrapped ComponentLoaderPolicy instance.
+//
+// The object is single use only, it will be deleted when ComponentLoaded or
+// ComponentLoadedFailed is called once.
+//
+// Can be called on a thread that is different from the thread the object is
+// created on.
+class AndroidComponentLoaderPolicy {
+ public:
+  explicit AndroidComponentLoaderPolicy(
+      std::unique_ptr<ComponentLoaderPolicy> loader_policy);
+  ~AndroidComponentLoaderPolicy();
+
+  AndroidComponentLoaderPolicy(const AndroidComponentLoaderPolicy&) = delete;
+  AndroidComponentLoaderPolicy& operator=(const AndroidComponentLoaderPolicy&) =
+      delete;
+
+  // A utility method that returns an array of Java objects of
+  // `org.chromium.components.component_updater.ComponentLoaderPolicy`.
+  static base::android::ScopedJavaLocalRef<jobjectArray>
+  ToJavaArrayOfAndroidComponentLoaderPolicy(
+      JNIEnv* env,
+      ComponentLoaderPolicyVector policies);
+
+  // JNI overrides:
+  void ComponentLoaded(JNIEnv* env,
+                       const base::android::JavaRef<jobjectArray>& jfile_names,
+                       const base::android::JavaRef<jintArray>& jfds);
+  void ComponentLoadFailed(JNIEnv* env);
+  base::android::ScopedJavaLocalRef<jstring> GetComponentId(JNIEnv* env);
+
+ private:
+  // Returns a Java object of
+  // `org.chromium.components.component_updater.ComponentLoaderPolicy`.
+  base::android::ScopedJavaLocalRef<jobject> GetJavaObject();
+
+  void NotifyNewVersion(const base::flat_map<std::string, int>& fd_map,
+                        std::unique_ptr<base::DictionaryValue> manifest);
+  void CloseFdsAndFail(const base::flat_map<std::string, int>& fd_map);
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // A Java object of
+  // `org.chromium.components.component_updater.ComponentLoaderPolicy`.
+  base::android::ScopedJavaGlobalRef<jobject> obj_;
+
+  std::unique_ptr<ComponentLoaderPolicy> loader_policy_;
+};
+
+}  // namespace component_updater
+
+#endif  // COMPONENTS_COMPONENT_UPDATER_ANDROID_COMPONENT_LOADER_POLICY_H_
\ No newline at end of file
diff --git a/components/component_updater/android/embedded_component_loader_unittests.cc b/components/component_updater/android/component_loader_policy_unittests.cc
similarity index 65%
rename from components/component_updater/android/embedded_component_loader_unittests.cc
rename to components/component_updater/android/component_loader_policy_unittests.cc
index e17849f..b5547f1a 100644
--- a/components/component_updater/android/embedded_component_loader_unittests.cc
+++ b/components/component_updater/android/component_loader_policy_unittests.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/component_updater/android/embedded_component_loader.h"
+#include "components/component_updater/android/component_loader_policy.h"
 
 #include <fcntl.h>
 #include <jni.h>
@@ -109,14 +109,15 @@
 
 }  // namespace
 
-class EmbeddedComponentLoaderTest : public testing::Test {
+class AndroidComponentLoaderPolicyTest : public testing::Test {
  public:
-  EmbeddedComponentLoaderTest() = default;
-  ~EmbeddedComponentLoaderTest() override = default;
+  AndroidComponentLoaderPolicyTest() = default;
+  ~AndroidComponentLoaderPolicyTest() override = default;
 
-  EmbeddedComponentLoaderTest(const EmbeddedComponentLoaderTest&) = delete;
-  EmbeddedComponentLoaderTest& operator=(const EmbeddedComponentLoaderTest&) =
+  AndroidComponentLoaderPolicyTest(const AndroidComponentLoaderPolicyTest&) =
       delete;
+  AndroidComponentLoaderPolicyTest& operator=(
+      const AndroidComponentLoaderPolicyTest&) = delete;
 
   void SetUp() override {
     env_ = base::android::AttachCurrentThread();
@@ -141,7 +142,7 @@
   base::ScopedTempDir temp_dir_;
 };
 
-TEST_F(EmbeddedComponentLoaderTest, TestValidManifest) {
+TEST_F(AndroidComponentLoaderPolicyTest, TestValidManifest) {
   base::test::TaskEnvironment task_environment;
 
   WriteFile("file1.txt", "1");
@@ -150,36 +151,38 @@
             "{\n\"manifest_version\": 2,\n\"version\": \"123.456.789\"\n}");
 
   base::RunLoop run_loop;
-  EmbeddedComponentLoader loader(std::make_unique<MockLoaderPolicy>(
-      base::BindOnce(&VerifyComponentLoaded, run_loop.QuitClosure()),
-      base::BindOnce([]() { FAIL(); })));
+  auto* android_policy =
+      new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>(
+          base::BindOnce(&VerifyComponentLoaded, run_loop.QuitClosure()),
+          base::BindOnce([]() { FAIL(); })));
 
-  loader.ComponentLoaded(env_,
-                         base::android::ToJavaArrayOfStrings(env_, files_),
-                         base::android::ToJavaIntArray(env_, GetFileFds()));
+  android_policy->ComponentLoaded(
+      env_, base::android::ToJavaArrayOfStrings(env_, files_),
+      base::android::ToJavaIntArray(env_, GetFileFds()));
   run_loop.Run();
 }
 
-TEST_F(EmbeddedComponentLoaderTest, TestMissingManifest) {
+TEST_F(AndroidComponentLoaderPolicyTest, TestMissingManifest) {
   base::test::TaskEnvironment task_environment;
 
   WriteFile("file.txt", "test");
 
   base::RunLoop run_loop;
-  EmbeddedComponentLoader loader(std::make_unique<MockLoaderPolicy>(
-      base::BindOnce(
-          [](const base::Version& version,
-             const base::flat_map<std::string, int>& fd_map,
-             std::unique_ptr<base::DictionaryValue> manifest) { FAIL(); }),
-      run_loop.QuitClosure()));
+  auto* android_policy =
+      new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>(
+          base::BindOnce(
+              [](const base::Version& version,
+                 const base::flat_map<std::string, int>& fd_map,
+                 std::unique_ptr<base::DictionaryValue> manifest) { FAIL(); }),
+          run_loop.QuitClosure()));
 
-  loader.ComponentLoaded(env_,
-                         base::android::ToJavaArrayOfStrings(env_, files_),
-                         base::android::ToJavaIntArray(env_, GetFileFds()));
+  android_policy->ComponentLoaded(
+      env_, base::android::ToJavaArrayOfStrings(env_, files_),
+      base::android::ToJavaIntArray(env_, GetFileFds()));
   run_loop.Run();
 }
 
-TEST_F(EmbeddedComponentLoaderTest, TestInvalidVersion) {
+TEST_F(AndroidComponentLoaderPolicyTest, TestInvalidVersion) {
   base::test::TaskEnvironment task_environment;
 
   WriteFile("file.txt", "test");
@@ -187,46 +190,49 @@
             "{\n\"manifest_version\": 2,\n\"version\": \"\"\n}");
 
   base::RunLoop run_loop;
-  EmbeddedComponentLoader loader(std::make_unique<MockLoaderPolicy>(
-      base::BindOnce(
-          [](const base::Version& version,
-             const base::flat_map<std::string, int>& fd_map,
-             std::unique_ptr<base::DictionaryValue> manifest) { FAIL(); }),
-      run_loop.QuitClosure()));
+  auto* android_policy =
+      new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>(
+          base::BindOnce(
+              [](const base::Version& version,
+                 const base::flat_map<std::string, int>& fd_map,
+                 std::unique_ptr<base::DictionaryValue> manifest) { FAIL(); }),
+          run_loop.QuitClosure()));
 
-  loader.ComponentLoaded(env_,
-                         base::android::ToJavaArrayOfStrings(env_, files_),
-                         base::android::ToJavaIntArray(env_, GetFileFds()));
+  android_policy->ComponentLoaded(
+      env_, base::android::ToJavaArrayOfStrings(env_, files_),
+      base::android::ToJavaIntArray(env_, GetFileFds()));
   run_loop.Run();
 }
 
-TEST_F(EmbeddedComponentLoaderTest, TestInvalidManifest) {
+TEST_F(AndroidComponentLoaderPolicyTest, TestInvalidManifest) {
   base::test::TaskEnvironment task_environment;
 
   WriteFile("file.txt", "test");
   WriteFile("manifest.json", "{\n\"manifest_version\":}");
 
   base::RunLoop run_loop;
-  EmbeddedComponentLoader loader(std::make_unique<MockLoaderPolicy>(
-      base::BindOnce(
-          [](const base::Version& version,
-             const base::flat_map<std::string, int>& fd_map,
-             std::unique_ptr<base::DictionaryValue> manifest) { FAIL(); }),
-      run_loop.QuitClosure()));
+  auto* android_policy =
+      new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>(
+          base::BindOnce(
+              [](const base::Version& version,
+                 const base::flat_map<std::string, int>& fd_map,
+                 std::unique_ptr<base::DictionaryValue> manifest) { FAIL(); }),
+          run_loop.QuitClosure()));
 
-  loader.ComponentLoaded(env_,
-                         base::android::ToJavaArrayOfStrings(env_, files_),
-                         base::android::ToJavaIntArray(env_, GetFileFds()));
+  android_policy->ComponentLoaded(
+      env_, base::android::ToJavaArrayOfStrings(env_, files_),
+      base::android::ToJavaIntArray(env_, GetFileFds()));
   run_loop.Run();
 }
 
-TEST_F(EmbeddedComponentLoaderTest, TestGetComponentId) {
+TEST_F(AndroidComponentLoaderPolicyTest, TestGetComponentId) {
   base::test::TaskEnvironment task_environment;
 
-  EmbeddedComponentLoader loader(std::make_unique<MockLoaderPolicy>());
+  auto* android_policy =
+      new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>());
 
   base::android::ScopedJavaLocalRef<jstring> jcomponentId =
-      loader.GetComponentId(env_);
+      android_policy->GetComponentId(env_);
 
   EXPECT_EQ(base::android::ConvertJavaStringToUTF8(jcomponentId), kComponentId);
 }
diff --git a/components/component_updater/android/embedded_component_loader.h b/components/component_updater/android/embedded_component_loader.h
deleted file mode 100644
index 9d6aeab..0000000
--- a/components/component_updater/android/embedded_component_loader.h
+++ /dev/null
@@ -1,101 +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 COMPONENTS_COMPONENT_UPDATER_ANDROID_EMBEDDED_COMPONENT_LOADER_H_
-#define COMPONENTS_COMPONENT_UPDATER_ANDROID_EMBEDDED_COMPONENT_LOADER_H_
-
-#include <jni.h>
-#include <stdint.h>
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/android/scoped_java_ref.h"
-#include "base/containers/flat_map.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/sequence_checker.h"
-
-namespace base {
-class Version;
-class DictionaryValue;
-}  // namespace base
-
-namespace component_updater {
-
-// Components should use `EmbeddedComponentLoader` by defining a class that
-// implements the members of `ComponentLoaderPolicy`, and then registering a
-// `EmbeddedComponentLoader` that has been constructed with an instance of that
-// class in an instance of embedded WebView or WebLayer with the Java
-// EmbeddedComponentLoader. The `EmbeddedComponentLoader` will fetch the
-// components files from the Android `ComponentsProviderService` and invoke the
-// callbacks defined in this class.
-//
-// Ideally, the implementation of this class should share implementation with
-// its component `ComponentInstallerPolicy` counterpart.
-class ComponentLoaderPolicy {
- public:
-  virtual ~ComponentLoaderPolicy();
-
-  // ComponentLoaded is called when the loader successfully gets file
-  // descriptors for all files in the component from the
-  // ComponentsProviderService.
-  //
-  // Must close all file descriptors after using them. Can be called multiple
-  // times in the same run.
-  //
-  // `version` is the version of the component.
-  // `fd_map` maps file relative paths in the install directory to its file
-  //          descriptor.
-  // `manifest` is the manifest for this version of the component.
-  virtual void ComponentLoaded(
-      const base::Version& version,
-      const base::flat_map<std::string, int>& fd_map,
-      std::unique_ptr<base::DictionaryValue> manifest) = 0;
-
-  // Called if connection to the service fails, components files are not found
-  // or if the manifest file is missing or invalid. Can
-  // be called multiple times in the same run.
-  //
-  // TODO(crbug.com/1180966) accept error code for different types of errors.
-  virtual void ComponentLoadFailed() = 0;
-
-  // Returns the component's SHA2 hash as raw bytes, the hash value is used as
-  // the unique id of the component and will be used to request components files
-  // from the ComponentsProviderService.
-  virtual void GetHash(std::vector<uint8_t>* hash) const = 0;
-};
-
-// Provides a bridge from Java to native to receive callbacks from the Java
-// loader and pass it to the wrapped ComponentLoaderPolicy instance.
-//
-// Must only be created and used on the same thread.
-class EmbeddedComponentLoader {
- public:
-  explicit EmbeddedComponentLoader(
-      std::unique_ptr<ComponentLoaderPolicy> loader_policy);
-  ~EmbeddedComponentLoader();
-
-  // JNI overrides:
-  void ComponentLoaded(JNIEnv* env,
-                       const base::android::JavaRef<jobjectArray>& jfile_names,
-                       const base::android::JavaRef<jintArray>& jfds);
-  void ComponentLoadFailed(JNIEnv* env);
-  base::android::ScopedJavaLocalRef<jstring> GetComponentId(JNIEnv* env);
-
- private:
-  SEQUENCE_CHECKER(sequence_checker_);
-
-  void NotifyNewVersion(const base::flat_map<std::string, int>& fd_map,
-                        std::unique_ptr<base::DictionaryValue> manifest);
-  void CloseFdsAndFail(const base::flat_map<std::string, int>& fd_map);
-
-  std::unique_ptr<ComponentLoaderPolicy> loader_policy_;
-  base::WeakPtrFactory<EmbeddedComponentLoader> weak_ptr_factory_{this};
-};
-
-}  // namespace component_updater
-
-#endif  // COMPONENTS_COMPONENT_UPDATER_ANDROID_EMBEDDED_COMPONENT_LOADER_H_
\ No newline at end of file
diff --git a/components/component_updater/android/java/src/org/chromium/components/component_updater/ComponentLoaderPolicyBridge.java b/components/component_updater/android/java/src/org/chromium/components/component_updater/ComponentLoaderPolicyBridge.java
new file mode 100644
index 0000000..a5ef448
--- /dev/null
+++ b/components/component_updater/android/java/src/org/chromium/components/component_updater/ComponentLoaderPolicyBridge.java
@@ -0,0 +1,113 @@
+// 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.
+
+package org.chromium.components.component_updater;
+
+import android.os.ParcelFileDescriptor;
+
+import org.chromium.base.LifetimeAssert;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+
+import java.util.Map;
+
+/**
+ * Provides JNI bridge to the native ComponentLoaderPolicy.
+ */
+@JNINamespace("component_updater")
+public class ComponentLoaderPolicyBridge {
+    private static final long NATIVE_NULL = 0;
+
+    private final LifetimeAssert mLifetimeAssert = LifetimeAssert.create(this);
+
+    private long mNativeAndroidComponentLoaderPolicy = NATIVE_NULL;
+
+    @CalledByNative
+    private ComponentLoaderPolicyBridge(long nativeAndroidComponentLoaderPolicy) {
+        mNativeAndroidComponentLoaderPolicy = nativeAndroidComponentLoaderPolicy;
+    }
+
+    /**
+     * ComponentLoaded is called when the loader successfully gets file descriptors for all files
+     * in the component from the ComponentsProviderService.
+     *
+     * Should close all file descriptors after using them. Can be called on a background thread.
+     *
+     * Exactly one of componentLoaded or componentLoadFailed should be called exactly once.
+     *
+     * @param fileMap maps file relative paths in the install directory to its file descriptor.
+     */
+    public void componentLoaded(Map<String, ParcelFileDescriptor> fileMap) {
+        assert mNativeAndroidComponentLoaderPolicy != NATIVE_NULL;
+
+        // Flatten the map into two arrays one for keys and another for values to be able to
+        // pass them to native.
+        String[] fileNames = new String[fileMap.size()];
+        int[] fds = new int[fileMap.size()];
+        int i = 0;
+        for (Map.Entry<String, ParcelFileDescriptor> file : fileMap.entrySet()) {
+            fileNames[i] = file.getKey();
+            fds[i] = file.getValue().getFd();
+            ++i;
+        }
+        ComponentLoaderPolicyBridgeJni.get().componentLoaded(
+                mNativeAndroidComponentLoaderPolicy, fileNames, fds);
+        // Setting it to null, because it is deleted after componentLoaded is called.
+        mNativeAndroidComponentLoaderPolicy = NATIVE_NULL;
+
+        // If mLifetimeAssert is GC'ed before this is called, it will throw an exception
+        // with a stack trace showing the stack during LifetimeAssert.create().
+        LifetimeAssert.setSafeToGc(mLifetimeAssert, true);
+    }
+
+    /**
+     * Called if connection to the service fails, components files are not found or if the manifest
+     * file is missing or invalid. Can be called on a background thread.
+     *
+     * Exactly one of componentLoaded or componentLoadFailed should be called exactly once.
+     */
+    public void componentLoadFailed() {
+        assert mNativeAndroidComponentLoaderPolicy != NATIVE_NULL;
+
+        ComponentLoaderPolicyBridgeJni.get().componentLoadFailed(
+                mNativeAndroidComponentLoaderPolicy);
+        // Setting it to null, because it is deleted after componentLoadFailed is called.
+        mNativeAndroidComponentLoaderPolicy = NATIVE_NULL;
+
+        // If mLifetimeAssert is GC'ed before this is called, it will throw an exception
+        // with a stack trace showing the stack during LifetimeAssert.create().
+        LifetimeAssert.setSafeToGc(mLifetimeAssert, true);
+    }
+
+    /**
+     * Returns the component's unique id by parsing its SHA2 will be used to request components
+     * files from the ComponentsProviderService. Can be called on a background thread.
+     */
+    public String getComponentId() {
+        assert mNativeAndroidComponentLoaderPolicy != NATIVE_NULL;
+
+        return ComponentLoaderPolicyBridgeJni.get().getComponentId(
+                mNativeAndroidComponentLoaderPolicy);
+    }
+
+    @CalledByNative
+    private static ComponentLoaderPolicyBridge[] createNewArray(int size) {
+        return new ComponentLoaderPolicyBridge[size];
+    }
+
+    @CalledByNative
+    private static void setArrayElement(
+            ComponentLoaderPolicyBridge[] array, int index, ComponentLoaderPolicyBridge policy) {
+        array[index] = policy;
+    }
+
+    @NativeMethods
+    interface Natives {
+        void componentLoaded(
+                long nativeAndroidComponentLoaderPolicy, String[] fileNames, int[] fds);
+        void componentLoadFailed(long nativeAndroidComponentLoaderPolicy);
+        String getComponentId(long nativeAndroidComponentLoaderPolicy);
+    }
+}
diff --git a/components/component_updater/android/java/src/org/chromium/components/component_updater/EmbeddedComponentLoader.java b/components/component_updater/android/java/src/org/chromium/components/component_updater/EmbeddedComponentLoader.java
new file mode 100644
index 0000000..8e8bc9e7
--- /dev/null
+++ b/components/component_updater/android/java/src/org/chromium/components/component_updater/EmbeddedComponentLoader.java
@@ -0,0 +1,151 @@
+// 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.
+
+package org.chromium.components.component_updater;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.task.PostTask;
+import org.chromium.base.task.TaskRunner;
+import org.chromium.base.task.TaskTraits;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * ComponentLoader that is used in embedded WebViews/WebLayers. It implements a ServiceConnection to
+ * connect to the provider service to fetch components files.
+ */
+public class EmbeddedComponentLoader implements ServiceConnection {
+    private static final String TAG = "EmbedComponentLoader";
+
+    private static final String KEY_RESULT = "RESULT";
+
+    private final TaskRunner mSequencedTaskRunner =
+            PostTask.createSequencedTaskRunner(TaskTraits.BEST_EFFORT_MAY_BLOCK);
+
+    // Maintain a set of ComponentResultReceivers, remove a receiver once it gets a result back.
+    // When a connection is established or restablished we request files from the service for the
+    // components remaining in the set. Unbind the service when the set is empty, i.e all results
+    // are received.
+    // Must be only accessed on mSequencedTaskRunner.
+    private final Set<ComponentResultReceiver> mComponentsResultReceivers = new HashSet<>();
+
+    public EmbeddedComponentLoader(Collection<ComponentLoaderPolicyBridge> componentLoaderPolicy) {
+        mSequencedTaskRunner.postTask(() -> {
+            for (ComponentLoaderPolicyBridge policy : componentLoaderPolicy) {
+                mComponentsResultReceivers.add(new ComponentResultReceiver(policy));
+            }
+        });
+    }
+
+    private class ComponentResultReceiver extends ResultReceiver {
+        private final ComponentLoaderPolicyBridge mComponent;
+
+        public ComponentResultReceiver(ComponentLoaderPolicyBridge component) {
+            // Receive results on arbitrary threads, we will post it back on mSequencedTaskRunner
+            // anyway.
+            super(/* handler= */ null);
+            mComponent = component;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mSequencedTaskRunner.postTask(() -> {
+                // Already removed, i.e has already received the result.
+                if (!mComponentsResultReceivers.remove(this)) {
+                    return;
+                }
+                // Only unbind when all results are received because it's a bound service and if we
+                // unbind the connection before getting all results back, the service might be
+                // killed before sending all results.
+                if (mComponentsResultReceivers.isEmpty()) {
+                    ContextUtils.getApplicationContext().unbindService(
+                            EmbeddedComponentLoader.this);
+                }
+
+                if (resultCode != 0) {
+                    mComponent.componentLoadFailed();
+                    return;
+                }
+                Map<String, ParcelFileDescriptor> resultMap =
+                        (Map<String, ParcelFileDescriptor>) resultData.getSerializable(KEY_RESULT);
+                if (resultMap == null) {
+                    mComponent.componentLoadFailed();
+                    return;
+                }
+                mComponent.componentLoaded(resultMap);
+            });
+        }
+
+        public ComponentLoaderPolicyBridge getComponentLoaderPolicy() {
+            return mComponent;
+        }
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        mSequencedTaskRunner.postTask(() -> {
+            try {
+                IComponentsProviderService providerService =
+                        IComponentsProviderService.Stub.asInterface(service);
+                for (ComponentResultReceiver receiver : mComponentsResultReceivers) {
+                    String componentId = receiver.getComponentLoaderPolicy().getComponentId();
+                    providerService.getFilesForComponent(componentId, receiver);
+                }
+            } catch (RemoteException e) {
+                Log.d(TAG, "Remote Exception calling ComponentProviderService", e);
+                if (!mComponentsResultReceivers.isEmpty()) {
+                    // Clearing up receivers here to avoid unbinding multiple times in the future.
+                    // This means if some receivers get their result after this step, their results
+                    // will be ignored.
+                    for (ComponentResultReceiver receiver : mComponentsResultReceivers) {
+                        receiver.getComponentLoaderPolicy().componentLoadFailed();
+                    }
+                    mComponentsResultReceivers.clear();
+                    ContextUtils.getApplicationContext().unbindService(this);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName className) {}
+
+    /**
+     * Bind to the provider service with the given {@code intent} and load components.
+     *
+     * Only connect to the service if there are registered components when the class is created.
+     * Must be called once.
+     *
+     * @param intent to connect to the service.
+     */
+    public void connect(Intent intent) {
+        mSequencedTaskRunner.postTask(() -> {
+            if (mComponentsResultReceivers.isEmpty()) {
+                return;
+            }
+            final Context appContext = ContextUtils.getApplicationContext();
+            if (!appContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
+                Log.d(TAG, "Could not bind to " + intent);
+                for (ComponentResultReceiver receiver : mComponentsResultReceivers) {
+                    receiver.getComponentLoaderPolicy().componentLoadFailed();
+                }
+                mComponentsResultReceivers.clear();
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/components/component_updater/android/loader_policies/trust_token_key_commitments_component_loader_policy.cc b/components/component_updater/android/loader_policies/trust_token_key_commitments_component_loader_policy.cc
new file mode 100644
index 0000000..cd55c8d5
--- /dev/null
+++ b/components/component_updater/android/loader_policies/trust_token_key_commitments_component_loader_policy.cc
@@ -0,0 +1,88 @@
+// 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/component_updater/android/loader_policies/trust_token_key_commitments_component_loader_policy.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/optional.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "components/component_updater/installer_policies/trust_token_key_commitments_component_installer_policy.h"
+
+namespace {
+
+// Attempts to load key commitments as raw JSON from their storage file,
+// returning the loaded commitments on success and nullopt on failure.
+// TODO(crbug.com/1180964) move reading string from fd to base/file_util.h
+base::Optional<std::string> LoadKeyCommitmentsFromDisk(int fd) {
+  base::ScopedFILE file_stream(fdopen(fd, "r"));
+  if (!file_stream.get()) {
+    return base::nullopt;
+  }
+  std::string commitments;
+  if (!base::ReadStreamToString(file_stream.get(), &commitments))
+    return base::nullopt;
+
+  return commitments;
+}
+
+}  // namespace
+
+namespace component_updater {
+
+TrustTokenKeyCommitmentsComponentLoaderPolicy::
+    TrustTokenKeyCommitmentsComponentLoaderPolicy(
+        base::RepeatingCallback<void(const std::string&)> on_commitments_ready)
+    : on_commitments_ready_(std::move(on_commitments_ready)) {}
+
+TrustTokenKeyCommitmentsComponentLoaderPolicy::
+    ~TrustTokenKeyCommitmentsComponentLoaderPolicy() = default;
+
+void TrustTokenKeyCommitmentsComponentLoaderPolicy::ComponentLoaded(
+    const base::Version& version,
+    const base::flat_map<std::string, int>& fd_map,
+    std::unique_ptr<base::DictionaryValue> manifest) {
+  int keys_fd = -1;
+  for (auto& key_value : fd_map) {
+    if (key_value.first == kTrustTokenKeyCommitmentsFileName) {
+      keys_fd = key_value.second;
+    } else {
+      // Close unused fds.
+      close(key_value.second);
+    }
+  }
+  if (keys_fd == -1) {
+    VLOG(1) << "TrustTokenKeyCommitmentsComponentLoaderPolicy#ComponentLoaded "
+               "failed because keys.json is not found in the fd map";
+    ComponentLoadFailed();
+    return;
+  }
+  component_updater::TrustTokenKeyCommitmentsComponentInstallerPolicy::
+      LoadTrustTokensFromString(
+          base::BindOnce(&LoadKeyCommitmentsFromDisk, keys_fd),
+          on_commitments_ready_);
+}
+
+void TrustTokenKeyCommitmentsComponentLoaderPolicy::ComponentLoadFailed() {}
+
+void TrustTokenKeyCommitmentsComponentLoaderPolicy::GetHash(
+    std::vector<uint8_t>* hash) const {
+  component_updater::TrustTokenKeyCommitmentsComponentInstallerPolicy::
+      GetPublicKeyHash(hash);
+}
+
+}  // namespace component_updater
diff --git a/components/component_updater/android/loader_policies/trust_token_key_commitments_component_loader_policy.h b/components/component_updater/android/loader_policies/trust_token_key_commitments_component_loader_policy.h
new file mode 100644
index 0000000..9652cdb1
--- /dev/null
+++ b/components/component_updater/android/loader_policies/trust_token_key_commitments_component_loader_policy.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 COMPONENTS_COMPONENT_UPDATER_ANDROID_LOADER_POLICIES_TRUST_TOKEN_KEY_COMMITMENTS_COMPONENT_LOADER_POLICY_H_
+#define COMPONENTS_COMPONENT_UPDATER_ANDROID_LOADER_POLICIES_TRUST_TOKEN_KEY_COMMITMENTS_COMPONENT_LOADER_POLICY_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "components/component_updater/android/component_loader_policy.h"
+
+namespace base {
+class DictionaryValue;
+class Version;
+}  // namespace base
+
+namespace component_updater {
+
+// TrustTokenKeyCommitmentsComponentLoaderPolicy defines a loader responsible
+// for receiving updated Trust Tokens key commitments config and passing them to
+// the network service via Mojo.
+class TrustTokenKeyCommitmentsComponentLoaderPolicy
+    : public ComponentLoaderPolicy {
+ public:
+  // |on_commitments_ready| will be called on the UI thread when
+  // key commitments become ready.
+  explicit TrustTokenKeyCommitmentsComponentLoaderPolicy(
+      base::RepeatingCallback<void(const std::string&)> on_commitments_ready);
+  ~TrustTokenKeyCommitmentsComponentLoaderPolicy() override;
+
+  TrustTokenKeyCommitmentsComponentLoaderPolicy(
+      const TrustTokenKeyCommitmentsComponentLoaderPolicy&) = delete;
+  TrustTokenKeyCommitmentsComponentLoaderPolicy& operator=(
+      const TrustTokenKeyCommitmentsComponentLoaderPolicy&) = delete;
+
+ private:
+  // The following methods override ComponentLoaderPolicy.
+  void ComponentLoaded(
+      const base::Version& version,
+      const base::flat_map<std::string, int>& fd_map,
+      std::unique_ptr<base::DictionaryValue> manifest) override;
+  void ComponentLoadFailed() override;
+  void GetHash(std::vector<uint8_t>* hash) const override;
+
+  base::RepeatingCallback<void(const std::string&)> on_commitments_ready_;
+};
+
+}  // namespace component_updater
+
+#endif  // COMPONENTS_COMPONENT_UPDATER_ANDROID_LOADER_POLICIES_TRUST_TOKEN_KEY_COMMITMENTS_COMPONENT_LOADER_POLICY_H_
diff --git a/components/component_updater/installer_policies/trust_token_key_commitments_component_installer_policy.cc b/components/component_updater/installer_policies/trust_token_key_commitments_component_installer_policy.cc
index 8b56f9a..0d50f855 100644
--- a/components/component_updater/installer_policies/trust_token_key_commitments_component_installer_policy.cc
+++ b/components/component_updater/installer_policies/trust_token_key_commitments_component_installer_policy.cc
@@ -27,11 +27,6 @@
 
 namespace {
 
-// This file name must be in sync with the server-side configuration, or updates
-// will fail.
-const base::FilePath::CharType kTrustTokenKeyCommitmentsFileName[] =
-    FILE_PATH_LITERAL("keys.json");
-
 // The SHA256 of the SubjectPublicKeyInfo used to sign the extension.
 // The extension id is: kiabhabjdbkjdpjbpigfodbdjmbglcoo
 const uint8_t kTrustTokenKeyCommitmentsPublicKeySHA256[32] = {
@@ -115,21 +110,9 @@
   VLOG(1) << "Component ready, version " << version.GetString() << " in "
           << install_dir.value();
 
-  base::ThreadPool::PostTaskAndReplyWithResult(
-      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
-      base::BindOnce(&LoadKeyCommitmentsFromDisk,
-                     GetInstalledPath(install_dir)),
-      base::BindOnce(
-          // Only bother sending commitments to the network service if we loaded
-          // them successfully.
-          [](base::RepeatingCallback<void(const std::string&)>
-                 on_commitments_ready,
-             base::Optional<std::string> loaded_commitments) {
-            if (loaded_commitments.has_value()) {
-              on_commitments_ready.Run(*loaded_commitments);
-            }
-          },
-          on_commitments_ready_));
+  LoadTrustTokensFromString(base::BindOnce(&LoadKeyCommitmentsFromDisk,
+                                           GetInstalledPath(install_dir)),
+                            on_commitments_ready_);
 }
 
 // Called during startup and installation before ComponentReady().
@@ -149,9 +132,7 @@
 
 void TrustTokenKeyCommitmentsComponentInstallerPolicy::GetHash(
     std::vector<uint8_t>* hash) const {
-  hash->assign(kTrustTokenKeyCommitmentsPublicKeySHA256,
-               kTrustTokenKeyCommitmentsPublicKeySHA256 +
-                   base::size(kTrustTokenKeyCommitmentsPublicKeySHA256));
+  GetPublicKeyHash(hash);
 }
 
 std::string TrustTokenKeyCommitmentsComponentInstallerPolicy::GetName() const {
@@ -164,4 +145,33 @@
   return update_client::InstallerAttributes();
 }
 
+// static
+void TrustTokenKeyCommitmentsComponentInstallerPolicy::GetPublicKeyHash(
+    std::vector<uint8_t>* hash) {
+  DCHECK(hash);
+  hash->assign(kTrustTokenKeyCommitmentsPublicKeySHA256,
+               kTrustTokenKeyCommitmentsPublicKeySHA256 +
+                   base::size(kTrustTokenKeyCommitmentsPublicKeySHA256));
+}
+
+// static
+void TrustTokenKeyCommitmentsComponentInstallerPolicy::
+    LoadTrustTokensFromString(
+        base::OnceCallback<base::Optional<std::string>()> load_keys_from_disk,
+        base::OnceCallback<void(const std::string&)> on_commitments_ready) {
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+      std::move(load_keys_from_disk),
+      base::BindOnce(
+          // Only bother sending commitments to the network service if we loaded
+          // them successfully.
+          [](base::OnceCallback<void(const std::string&)> on_commitments_ready,
+             base::Optional<std::string> loaded_commitments) {
+            if (loaded_commitments.has_value()) {
+              std::move(on_commitments_ready).Run(loaded_commitments.value());
+            }
+          },
+          std::move(on_commitments_ready)));
+}
+
 }  // namespace component_updater
diff --git a/components/component_updater/installer_policies/trust_token_key_commitments_component_installer_policy.h b/components/component_updater/installer_policies/trust_token_key_commitments_component_installer_policy.h
index 897cbdb7..c6e3198 100644
--- a/components/component_updater/installer_policies/trust_token_key_commitments_component_installer_policy.h
+++ b/components/component_updater/installer_policies/trust_token_key_commitments_component_installer_policy.h
@@ -14,6 +14,7 @@
 #include "base/callback.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/values.h"
 #include "components/component_updater/component_installer.h"
 
@@ -23,6 +24,11 @@
 
 namespace component_updater {
 
+// This file name must be in sync with the server-side configuration, or updates
+// will fail.
+const base::FilePath::CharType kTrustTokenKeyCommitmentsFileName[] =
+    FILE_PATH_LITERAL("keys.json");
+
 // TrustTokenKeyCommitmentsComponentInstallerPolicy defines an installer
 // responsible for receiving updated Trust Tokens
 // (https://github.com/wicg/trust-token-api) key commitments and passing them to
@@ -41,6 +47,20 @@
   TrustTokenKeyCommitmentsComponentInstallerPolicy& operator=(
       const TrustTokenKeyCommitmentsComponentInstallerPolicy&) = delete;
 
+  // Returns the component's SHA2 hash as raw bytes.
+  static void GetPublicKeyHash(std::vector<uint8_t>* hash);
+
+  // Loads trust tokens from string using the given `load_keys_from_disk` call.
+  //
+  // static to allow sharing with the Android `ComponentLoaderPolicy`.
+  //
+  // `load_keys_from_disk` a callback that read trust tokens from file and
+  // return them as an optional string. `on_commitments_ready` loads trust
+  // tokens in network service.
+  static void LoadTrustTokensFromString(
+      base::OnceCallback<base::Optional<std::string>()> load_keys_from_disk,
+      base::OnceCallback<void(const std::string&)> on_commitments_ready);
+
  protected:
   void GetHash(std::vector<uint8_t>* hash) const override;
 
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/ScopeChangeController.java b/components/messages/android/internal/java/src/org/chromium/components/messages/ScopeChangeController.java
index f75502f3..c2b4268 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/ScopeChangeController.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/ScopeChangeController.java
@@ -53,7 +53,7 @@
 
     void stopObservation(Object messageKey) {
         WebContents webContents = mMessageToWebContentsMap.get(messageKey);
-        assert webContents != null;
+        if (webContents == null) return;
         mMessageToWebContentsMap.remove(messageKey);
         RefCountWebContentsObserver webObserver =
                 mRefCountedWebContentsObserverMap.get(webContents);
diff --git a/components/omnibox/browser/autocomplete_provider.h b/components/omnibox/browser/autocomplete_provider.h
index feb3d15..e08940cd 100644
--- a/components/omnibox/browser/autocomplete_provider.h
+++ b/components/omnibox/browser/autocomplete_provider.h
@@ -315,6 +315,13 @@
   static bool IsExplicitlyInKeywordMode(const AutocompleteInput& input,
                                         const base::string16& keyword);
 
+  // Trims "http:" or "https:" and up to two subsequent slashes from |url|. If
+  // |trim_https| is true, trims "https:", otherwise trims "http:". Returns the
+  // number of characters that were trimmed.
+  // NOTE: For a view-source: URL, this will trim from after "view-source:" and
+  // return 0.
+  static size_t TrimSchemePrefix(base::string16* url, bool trim_https);
+
  protected:
   friend class base::RefCountedThreadSafe<AutocompleteProvider>;
   FRIEND_TEST_ALL_PREFIXES(BookmarkProviderTest, InlineAutocompletion);
@@ -341,13 +348,6 @@
   // string unconditionally.
   static FixupReturn FixupUserInput(const AutocompleteInput& input);
 
-  // Trims "http:" or "https:" and up to two subsequent slashes from |url|. If
-  // |trim_https| is true, trims "https:", otherwise trims "http:". Returns the
-  // number of characters that were trimmed.
-  // NOTE: For a view-source: URL, this will trim from after "view-source:" and
-  // return 0.
-  static size_t TrimSchemePrefix(base::string16* url, bool trim_https);
-
   const size_t provider_max_matches_;
 
   ACMatches matches_;
diff --git a/components/omnibox/browser/clipboard_provider.cc b/components/omnibox/browser/clipboard_provider.cc
index c912ee14..0078dab 100644
--- a/components/omnibox/browser/clipboard_provider.cc
+++ b/components/omnibox/browser/clipboard_provider.cc
@@ -254,9 +254,8 @@
         (base::FeatureList::IsEnabled(omnibox::kDisplayTitleForCurrentUrl))
             ? input.current_title()
             : base::string16();
-    AutocompleteMatch verbatim_match =
-        VerbatimMatchForURL(client_, input, input.current_url(), description,
-                            history_url_provider_, -1);
+    AutocompleteMatch verbatim_match = VerbatimMatchForURL(
+        this, client_, input, input.current_url(), description, -1);
     matches_.push_back(verbatim_match);
   }
 
diff --git a/components/omnibox/browser/history_url_provider.cc b/components/omnibox/browser/history_url_provider.cc
index e49f886e..f920624 100644
--- a/components/omnibox/browser/history_url_provider.cc
+++ b/components/omnibox/browser/history_url_provider.cc
@@ -35,6 +35,7 @@
 #include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/omnibox/browser/url_index_private_data.h"
 #include "components/omnibox/browser/url_prefix.h"
+#include "components/omnibox/browser/verbatim_match.h"
 #include "components/omnibox/common/omnibox_features.h"
 #include "components/prefs/pref_service.h"
 #include "components/search_engines/omnibox_focus_type.h"
@@ -526,8 +527,9 @@
 
   // Create a match for what the user typed.
   const bool trim_http = !AutocompleteInput::HasHTTPScheme(input.text());
-  AutocompleteMatch what_you_typed_match(SuggestExactInput(
-      fixed_up_input, fixed_up_input.canonicalized_url(), trim_http));
+  AutocompleteMatch what_you_typed_match(
+      VerbatimMatchForInput(this, client(), fixed_up_input,
+                            fixed_up_input.canonicalized_url(), trim_http));
 
   // If the input fix-up above added characters, show them as an
   // autocompletion, unless directed not to.
@@ -621,76 +623,6 @@
   return res;
 }
 
-AutocompleteMatch HistoryURLProvider::SuggestExactInput(
-    const AutocompleteInput& input,
-    const GURL& destination_url,
-    bool trim_default_scheme) {
-  // The FormattedStringWithEquivalentMeaning() call below requires callers to
-  // be on the main thread.
-  DCHECK(thread_checker_.CalledOnValidThread());
-
-  AutocompleteMatch match(this, 0, false,
-                          AutocompleteMatchType::URL_WHAT_YOU_TYPED);
-
-  if (destination_url.is_valid()) {
-    match.destination_url = destination_url;
-    // If the input explicitly contains "http://" or "https://", callers must
-    // set |trim_default_scheme| to false. Otherwise, |trim_default_scheme| may
-    // be either true or false.
-    if (input.added_default_scheme_to_typed_url()) {
-      DCHECK(!(trim_default_scheme &&
-               AutocompleteInput::HasHTTPSScheme(input.text())));
-    } else {
-      DCHECK(!(trim_default_scheme &&
-               AutocompleteInput::HasHTTPScheme(input.text())));
-    }
-    const url_formatter::FormatUrlType format_type =
-        input.added_default_scheme_to_typed_url()
-            ? url_formatter::kFormatUrlOmitHTTPS
-            : url_formatter::kFormatUrlOmitHTTP;
-
-    base::string16 display_string(url_formatter::FormatUrl(
-        destination_url, url_formatter::kFormatUrlOmitDefaults & ~format_type,
-        net::UnescapeRule::SPACES, nullptr, nullptr, nullptr));
-    if (trim_default_scheme) {
-      TrimSchemePrefix(&display_string,
-                       input.added_default_scheme_to_typed_url());
-    }
-
-    match.fill_into_edit =
-        AutocompleteInput::FormattedStringWithEquivalentMeaning(
-            destination_url, display_string, client()->GetSchemeClassifier(),
-            nullptr);
-    // The what-you-typed match is generally only allowed to be default for
-    // URL inputs or when there is no default search provider.  (It's also
-    // allowed to be default for UNKNOWN inputs where the destination is a known
-    // intranet site.  In this case, |allowed_to_be_default_match| is revised in
-    // FixupExactSuggestion().)
-    const bool has_default_search_provider =
-       client()->GetTemplateURLService() &&
-       client()->GetTemplateURLService()->GetDefaultSearchProvider();
-    match.allowed_to_be_default_match =
-        (input.type() == metrics::OmniboxInputType::URL) ||
-        !has_default_search_provider;
-    // NOTE: Don't set match.inline_autocompletion to something non-empty here;
-    // it's surprising and annoying.
-
-    // Try to highlight "innermost" match location.  If we fix up "w" into
-    // "www.w.com", we want to highlight the fifth character, not the first.
-    // This relies on match.destination_url being the non-prefix-trimmed version
-    // of match.contents.
-    match.contents = display_string;
-
-    TermMatches termMatches = {{0, 0, input.text().length()}};
-    match.contents_class = ClassifyTermMatches(
-        termMatches, match.contents.size(),
-        ACMatchClassification::MATCH | ACMatchClassification::URL,
-        ACMatchClassification::URL);
-  }
-
-  return match;
-}
-
 void HistoryURLProvider::ExecuteWithDB(HistoryURLProviderParams* params,
                                        history::HistoryBackend* backend,
                                        history::URLDatabase* db) {
@@ -828,7 +760,7 @@
   // Check whether what the user typed appears in history.
   const bool can_check_history_for_exact_match =
       // Checking what_you_typed_match.destination_url.is_valid() tells us
-      // whether SuggestExactInput() succeeded in constructing a valid match.
+      // whether VerbatimMatchForInput succeeded in constructing a valid match.
       params->what_you_typed_match.destination_url.is_valid() &&
       // Additionally, in the case where the user has typed "foo.com" and
       // visited (but not typed) "foo/", and the input is "foo", the first pass
diff --git a/components/omnibox/browser/history_url_provider.h b/components/omnibox/browser/history_url_provider.h
index 2bb9f35..8f63cfce 100644
--- a/components/omnibox/browser/history_url_provider.h
+++ b/components/omnibox/browser/history_url_provider.h
@@ -43,7 +43,7 @@
 //   -----------                --------------
 //   AutocompleteController::Start
 //     -> HistoryURLProvider::Start
-//       -> SuggestExactInput
+//       -> VerbatimMatchForInput
 //       [params_ allocated]
 //       -> DoAutocomplete (for inline autocomplete)
 //         -> URLDatabase::AutocompleteForPrefix (on in-memory DB)
@@ -208,19 +208,6 @@
   // See base/trace_event/memory_usage_estimator.h for more info.
   size_t EstimateMemoryUsage() const override;
 
-  // Returns a match representing a navigation to |destination_url|, highlighted
-  // appropriately against |input|.  |trim_default_scheme| controls whether the
-  // match's |fill_into_edit| and |contents| should have the scheme (http or
-  // https only) stripped off, and should not be set to true if the user's
-  // original input contains the scheme. The default scheme is https if |input|
-  // is upgraded to https, otherwise it's http.
-  // NOTES: This does not set the relevance of the returned match, as different
-  //        callers want different behavior. Callers must set this manually.
-  //        This function should only be called on the UI thread.
-  AutocompleteMatch SuggestExactInput(const AutocompleteInput& input,
-                                      const GURL& destination_url,
-                                      bool trim_default_scheme);
-
   // Runs the history query on the history thread, called by the history
   // system. The history database MAY BE NULL in which case it is not
   // available and we should return no data. Also schedules returning the
diff --git a/components/omnibox/browser/history_url_provider_unittest.cc b/components/omnibox/browser/history_url_provider_unittest.cc
index b7bb1c6..e3a74b8 100644
--- a/components/omnibox/browser/history_url_provider_unittest.cc
+++ b/components/omnibox/browser/history_url_provider_unittest.cc
@@ -30,6 +30,7 @@
 #include "components/omnibox/browser/autocomplete_result.h"
 #include "components/omnibox/browser/fake_autocomplete_provider_client.h"
 #include "components/omnibox/browser/history_quick_provider.h"
+#include "components/omnibox/browser/verbatim_match.h"
 #include "components/omnibox/common/omnibox_features.h"
 #include "components/prefs/pref_service.h"
 #include "components/search_engines/default_search_manager.h"
@@ -1150,8 +1151,9 @@
                             metrics::OmniboxEventProto::BLANK,
                             TestSchemeClassifier());
     input.set_current_url(GURL("about:blank"));
-    AutocompleteMatch match(autocomplete_->SuggestExactInput(
-        input, input.canonicalized_url(), test_cases[i].trim_http));
+    AutocompleteMatch match(VerbatimMatchForInput(
+        autocomplete_.get(), client_.get(), input, input.canonicalized_url(),
+        test_cases[i].trim_http));
     EXPECT_EQ(ASCIIToUTF16(test_cases[i].contents), match.contents);
     for (size_t match_index = 0; match_index < match.contents_class.size();
          ++match_index) {
diff --git a/components/omnibox/browser/omnibox_edit_model.cc b/components/omnibox/browser/omnibox_edit_model.cc
index c16138a..93cba7f 100644
--- a/components/omnibox/browser/omnibox_edit_model.cc
+++ b/components/omnibox/browser/omnibox_edit_model.cc
@@ -43,6 +43,7 @@
 #include "components/omnibox/browser/omnibox_view.h"
 #include "components/omnibox/browser/search_provider.h"
 #include "components/omnibox/browser/suggestion_answer.h"
+#include "components/omnibox/browser/verbatim_match.h"
 #include "components/omnibox/common/omnibox_features.h"
 #include "components/search_engines/omnibox_focus_type.h"
 #include "components/search_engines/template_url.h"
@@ -622,9 +623,10 @@
     input.set_want_asynchronous_matches(input_.want_asynchronous_matches());
     input.set_focus_type(input_.focus_type());
     input_ = input;
-    AutocompleteMatch url_match(
-        autocomplete_controller()->history_url_provider()->SuggestExactInput(
-            input_, input_.canonicalized_url(), false));
+    AutocompleteMatch url_match(VerbatimMatchForInput(
+        autocomplete_controller()->history_url_provider(),
+        autocomplete_controller()->autocomplete_provider_client(), input_,
+        input_.canonicalized_url(), false));
 
     if (url_match.destination_url.is_valid()) {
       // We have a valid URL, we use this newly generated AutocompleteMatch.
@@ -758,7 +760,7 @@
   base::string16 input_text(pasted_text);
   if (input_text.empty())
     input_text = user_input_in_progress_ ? user_text_ : url_for_editing_;
-  // Create a dummy AutocompleteInput for use in calling SuggestExactInput()
+  // Create a dummy AutocompleteInput for use in calling VerbatimMatchForInput()
   // to create an alternate navigational match.
   AutocompleteInput alternate_input(
       input_text, GetPageClassification(), client_->GetSchemeClassifier(),
@@ -771,7 +773,9 @@
   std::unique_ptr<OmniboxNavigationObserver> observer(
       client_->CreateOmniboxNavigationObserver(
           input_text, match,
-          autocomplete_controller()->history_url_provider()->SuggestExactInput(
+          VerbatimMatchForInput(
+              autocomplete_controller()->history_url_provider(),
+              autocomplete_controller()->autocomplete_provider_client(),
               alternate_input, alternate_nav_url, false)));
 
   base::TimeDelta elapsed_time_since_last_change_to_default_match(
diff --git a/components/omnibox/browser/verbatim_match.cc b/components/omnibox/browser/verbatim_match.cc
index 3ddef34f..d0a4a3cd 100644
--- a/components/omnibox/browser/verbatim_match.cc
+++ b/components/omnibox/browser/verbatim_match.cc
@@ -6,28 +6,29 @@
 
 #include "components/omnibox/browser/autocomplete_classifier.h"
 #include "components/omnibox/browser/autocomplete_input.h"
+#include "components/omnibox/browser/autocomplete_match_classification.h"
+#include "components/omnibox/browser/autocomplete_provider.h"
 #include "components/omnibox/browser/autocomplete_provider_client.h"
-#include "components/omnibox/browser/history_url_provider.h"
+#include "components/omnibox/browser/in_memory_url_index_types.h"
+#include "components/search_engines/template_url_service.h"
 #include "url/gurl.h"
 
 AutocompleteMatch VerbatimMatchForURL(
+    AutocompleteProvider* provider,
     AutocompleteProviderClient* client,
     const AutocompleteInput& input,
     const GURL& destination_url,
     const base::string16& destination_description,
-    HistoryURLProvider* history_url_provider,
     int verbatim_relevance) {
   AutocompleteMatch match;
-  // If the caller already knows where the verbatim match should go and has
-  // provided a HistoryURLProvider to aid in its construction, construct the
-  // match directly, don't call Classify() on the input.  Classify() runs all
-  // providers' synchronous passes.  Some providers such as HistoryQuick can
-  // have a slow synchronous pass on some inputs.
-  if (history_url_provider && destination_url.is_valid()) {
-    match = history_url_provider->SuggestExactInput(
-        input,
-        destination_url,
-        !AutocompleteInput::HasHTTPScheme(input.text()));
+  // If the caller is a provider and already knows where the verbatim match
+  // should go, construct the match directly, don't call Classify() on the
+  // input. Classify() runs all providers' synchronous passes. Some providers
+  // such as HistoryQuick can have a slow synchronous pass on some inputs.
+  if (provider != nullptr && destination_url.is_valid()) {
+    match =
+        VerbatimMatchForInput(provider, client, input, destination_url,
+                              !AutocompleteInput::HasHTTPScheme(input.text()));
     match.description = destination_description;
     if (!match.description.empty())
       match.description_class.push_back({0, ACMatchClassification::NONE});
@@ -43,3 +44,69 @@
       verbatim_relevance >= 0 ? verbatim_relevance : kDefaultVerbatimRelevance;
   return match;
 }
+
+AutocompleteMatch VerbatimMatchForInput(AutocompleteProvider* provider,
+                                        AutocompleteProviderClient* client,
+                                        const AutocompleteInput& input,
+                                        const GURL& destination_url,
+                                        bool trim_default_scheme) {
+  AutocompleteMatch match(provider, 0, false,
+                          AutocompleteMatchType::URL_WHAT_YOU_TYPED);
+
+  if (destination_url.is_valid()) {
+    match.destination_url = destination_url;
+    // If the input explicitly contains "http://" or "https://", callers must
+    // set |trim_default_scheme| to false. Otherwise, |trim_default_scheme| may
+    // be either true or false.
+    if (input.added_default_scheme_to_typed_url()) {
+      DCHECK(!(trim_default_scheme &&
+               AutocompleteInput::HasHTTPSScheme(input.text())));
+    } else {
+      DCHECK(!(trim_default_scheme &&
+               AutocompleteInput::HasHTTPScheme(input.text())));
+    }
+    const url_formatter::FormatUrlType format_type =
+        input.added_default_scheme_to_typed_url()
+            ? url_formatter::kFormatUrlOmitHTTPS
+            : url_formatter::kFormatUrlOmitHTTP;
+
+    base::string16 display_string(url_formatter::FormatUrl(
+        destination_url, url_formatter::kFormatUrlOmitDefaults & ~format_type,
+        net::UnescapeRule::SPACES, nullptr, nullptr, nullptr));
+    if (trim_default_scheme) {
+      AutocompleteProvider::TrimSchemePrefix(
+          &display_string, input.added_default_scheme_to_typed_url());
+    }
+    match.fill_into_edit =
+        AutocompleteInput::FormattedStringWithEquivalentMeaning(
+            destination_url, display_string, client->GetSchemeClassifier(),
+            nullptr);
+    // The what-you-typed match is generally only allowed to be default for
+    // URL inputs or when there is no default search provider.  (It's also
+    // allowed to be default for UNKNOWN inputs where the destination is a known
+    // intranet site.  In this case, |allowed_to_be_default_match| is revised in
+    // FixupExactSuggestion().)
+    const bool has_default_search_provider =
+        client->GetTemplateURLService() &&
+        client->GetTemplateURLService()->GetDefaultSearchProvider();
+    match.allowed_to_be_default_match =
+        (input.type() == metrics::OmniboxInputType::URL) ||
+        !has_default_search_provider;
+    // NOTE: Don't set match.inline_autocompletion to something non-empty here;
+    // it's surprising and annoying.
+
+    // Try to highlight "innermost" match location.  If we fix up "w" into
+    // "www.w.com", we want to highlight the fifth character, not the first.
+    // This relies on match.destination_url being the non-prefix-trimmed version
+    // of match.contents.
+    match.contents = display_string;
+
+    TermMatches termMatches = {{0, 0, input.text().length()}};
+    match.contents_class = ClassifyTermMatches(
+        termMatches, match.contents.size(),
+        ACMatchClassification::MATCH | ACMatchClassification::URL,
+        ACMatchClassification::URL);
+  }
+
+  return match;
+}
diff --git a/components/omnibox/browser/verbatim_match.h b/components/omnibox/browser/verbatim_match.h
index cc5354d..d5f77be 100644
--- a/components/omnibox/browser/verbatim_match.h
+++ b/components/omnibox/browser/verbatim_match.h
@@ -10,8 +10,8 @@
 #include "url/gurl.h"
 
 struct AutocompleteMatch;
+class AutocompleteProvider;
 class AutocompleteProviderClient;
-class HistoryURLProvider;
 
 // Returns a verbatim match for input.text() with a relevance of
 // |verbatim_relevance|. If |verbatim_relevance| is negative then a default
@@ -21,11 +21,26 @@
 // implementation for details) than the default code path.
 // input.text() must not be empty.
 AutocompleteMatch VerbatimMatchForURL(
+    AutocompleteProvider* provider,
     AutocompleteProviderClient* client,
     const AutocompleteInput& input,
     const GURL& destination_url,
     const base::string16& destination_description,
-    HistoryURLProvider* history_url_provider,
     int verbatim_relevance);
 
+// Returns a match representing a navigation to |destination_url|, highlighted
+// appropriately against |input|.  |trim_default_scheme| controls whether the
+// match's |fill_into_edit| and |contents| should have the scheme (http or
+// https only) stripped off, and should not be set to true if the user's
+// original input contains the scheme. The default scheme is https if |input|
+// is upgraded to https, otherwise it's http.
+// NOTES: This does not set the relevance of the returned match, as different
+//        callers want different behavior. Callers must set this manually.
+//        This function should only be called on the UI thread.
+AutocompleteMatch VerbatimMatchForInput(AutocompleteProvider* provider,
+                                        AutocompleteProviderClient* client,
+                                        const AutocompleteInput& input,
+                                        const GURL& destination_url,
+                                        bool trim_http);
+
 #endif  // COMPONENTS_OMNIBOX_BROWSER_VERBATIM_MATCH_H_
diff --git a/components/omnibox/browser/zero_suggest_provider.cc b/components/omnibox/browser/zero_suggest_provider.cc
index fa6e734a..65f5787 100644
--- a/components/omnibox/browser/zero_suggest_provider.cc
+++ b/components/omnibox/browser/zero_suggest_provider.cc
@@ -457,9 +457,9 @@
   // We pass a nullptr as the |history_url_provider| parameter now to force
   // VerbatimMatch to do a classification, since the text can be a search query.
   // TODO(tommycli): Simplify this - probably just bypass VerbatimMatchForURL.
-  AutocompleteMatch match = VerbatimMatchForURL(
-      client(), tmp, GURL(current_query_), description,
-      /*history_url_provider=*/nullptr, results_.verbatim_relevance);
+  AutocompleteMatch match =
+      VerbatimMatchForURL(this, client(), tmp, GURL(current_query_),
+                          description, results_.verbatim_relevance);
   match.provider = this;
   return match;
 }
diff --git a/components/omnibox/browser/zero_suggest_verbatim_match_provider.cc b/components/omnibox/browser/zero_suggest_verbatim_match_provider.cc
index fb58fe5..04d2e86 100644
--- a/components/omnibox/browser/zero_suggest_verbatim_match_provider.cc
+++ b/components/omnibox/browser/zero_suggest_verbatim_match_provider.cc
@@ -9,6 +9,8 @@
 #include "components/omnibox/browser/autocomplete_provider_listener.h"
 #include "components/omnibox/browser/verbatim_match.h"
 #include "components/omnibox/common/omnibox_features.h"
+#include "components/url_formatter/url_formatter.h"
+#include "net/base/escape.h"
 
 namespace {
 // The relevance score for verbatim match.
@@ -51,7 +53,8 @@
 
   // Only offer verbatim match after the user just focused the Omnibox,
   // or if the input field is empty.
-  if (input.focus_type() == OmniboxFocusType::DEFAULT)
+  if (input.focus_type() == OmniboxFocusType::DEFAULT ||
+      input.focus_type() == OmniboxFocusType::DELETED_PERMANENT_TEXT)
     return;
 
   // For consistency with other zero-prefix providers.
@@ -70,9 +73,14 @@
   verbatim_input.set_prevent_inline_autocomplete(true);
   verbatim_input.set_allow_exact_keyword_match(false);
 
-  AutocompleteMatch match = VerbatimMatchForURL(
-      client_, verbatim_input, page_url, input.current_title(), nullptr,
-      kVerbatimMatchRelevanceScore);
+  AutocompleteMatch match =
+      VerbatimMatchForURL(this, client_, verbatim_input, page_url,
+                          input.current_title(), kVerbatimMatchRelevanceScore);
+  // Make sure the URL is formatted the same was as most visited sites.
+  auto format_types = AutocompleteMatch::GetFormatTypes(false, false);
+  match.contents = url_formatter::FormatUrl(page_url, format_types,
+                                            net::UnescapeRule::SPACES, nullptr,
+                                            nullptr, nullptr);
 
   // In the case of native pages, the classifier may replace the URL with an
   // empty content, resulting with a verbatim match that does not point
diff --git a/components/omnibox/browser/zero_suggest_verbatim_match_provider.h b/components/omnibox/browser/zero_suggest_verbatim_match_provider.h
index 34f2708..f5d91d0 100644
--- a/components/omnibox/browser/zero_suggest_verbatim_match_provider.h
+++ b/components/omnibox/browser/zero_suggest_verbatim_match_provider.h
@@ -6,6 +6,7 @@
 
 #include "components/omnibox/browser/autocomplete_input.h"
 #include "components/omnibox/browser/autocomplete_provider.h"
+#include "components/omnibox/browser/history_url_provider.h"
 
 class AutocompleteProviderClient;
 
diff --git a/components/omnibox/browser/zero_suggest_verbatim_match_provider_unittest.cc b/components/omnibox/browser/zero_suggest_verbatim_match_provider_unittest.cc
index b7c99573..4b055e9 100644
--- a/components/omnibox/browser/zero_suggest_verbatim_match_provider_unittest.cc
+++ b/components/omnibox/browser/zero_suggest_verbatim_match_provider_unittest.cc
@@ -11,6 +11,8 @@
 
 #include "base/feature_list.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/task_environment.h"
+#include "components/omnibox/browser/fake_autocomplete_provider_client.h"
 #include "components/omnibox/browser/mock_autocomplete_provider_client.h"
 #include "components/omnibox/browser/test_scheme_classifier.h"
 #include "components/omnibox/common/omnibox_features.h"
@@ -27,8 +29,10 @@
 
  protected:
   bool IsVerbatimMatchEligible() const;
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::MainThreadType::UI};
   scoped_refptr<ZeroSuggestVerbatimMatchProvider> provider_;
-  MockAutocompleteProviderClient mock_client_;
+  FakeAutocompleteProviderClient mock_client_;
 };
 
 bool ZeroSuggestVerbatimMatchProviderTest::IsVerbatimMatchEligible() const {
diff --git a/components/omnibox/resources/BUILD.gn b/components/omnibox/resources/BUILD.gn
index f64c7e17..2b75ff01 100644
--- a/components/omnibox/resources/BUILD.gn
+++ b/components/omnibox/resources/BUILD.gn
@@ -8,7 +8,7 @@
 grit("omnibox_resources") {
   source = "omnibox_resources.grd"
   outputs = [ "grit/omnibox_resources.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "omnibox_resources_$locale.pak" ]
   }
 }
diff --git a/components/page_load_metrics/browser/DEPS b/components/page_load_metrics/browser/DEPS
index 28827d6..fb2f33a 100644
--- a/components/page_load_metrics/browser/DEPS
+++ b/components/page_load_metrics/browser/DEPS
@@ -2,11 +2,18 @@
   "+content/public/common",
   "+content/public/browser",
   "+content/public/test",
+  "+components/blocklist",
   "+components/data_reduction_proxy/core/browser",
+  "+components/heavy_ad_intervention",
   "+components/keyed_service/content",
   "+components/keyed_service/core",
   "+components/performance_manager/public",
+  "+components/subresource_filter/content/browser",
+  "+components/subresource_filter/core/browser",
+  "+components/subresource_filter/core/common",
+  "+components/subresource_filter/core/mojom",
   "+components/ukm",
+  "+mojo/public",
   "+net",
   "+services/metrics",
   "+services/network/public",
diff --git a/components/page_load_metrics/browser/observers/ad_metrics/BUILD.gn b/components/page_load_metrics/browser/observers/ad_metrics/BUILD.gn
new file mode 100644
index 0000000..2ec15bc
--- /dev/null
+++ b/components/page_load_metrics/browser/observers/ad_metrics/BUILD.gn
@@ -0,0 +1,51 @@
+# 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.
+
+source_set("ad_metrics") {
+  sources = [
+    "ads_page_load_metrics_observer.cc",
+    "ads_page_load_metrics_observer.h",
+    "frame_data.cc",
+    "frame_data.h",
+    "page_ad_density_tracker.cc",
+    "page_ad_density_tracker.h",
+  ]
+  deps = [
+    "//components/blocklist/opt_out_blocklist",
+    "//components/heavy_ad_intervention",
+    "//components/page_load_metrics/browser",
+    "//components/page_load_metrics/common",
+    "//components/page_load_metrics/common:page_load_metrics_mojom",
+    "//components/subresource_filter/content/browser",
+    "//components/subresource_filter/core/browser",
+    "//components/subresource_filter/core/common",
+    "//components/subresource_filter/core/mojom",
+    "//components/ukm/content",
+    "//content/public/browser",
+    "//services/metrics/public/cpp:ukm_builders",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "ads_page_load_metrics_observer_unittest.cc",
+    "page_ad_density_tracker_unittest.cc",
+  ]
+  deps = [
+    ":ad_metrics",
+    "//components/blocklist/opt_out_blocklist",
+    "//components/heavy_ad_intervention",
+    "//components/page_load_metrics/browser",
+    "//components/page_load_metrics/browser:test_support",
+    "//components/page_load_metrics/common",
+    "//components/page_load_metrics/common:test_support",
+    "//components/subresource_filter/content/browser",
+    "//components/subresource_filter/content/browser:test_support",
+    "//components/subresource_filter/core/common",
+    "//components/ukm:test_support",
+    "//content/test:test_support",
+    "//services/metrics/public/cpp:ukm_builders",
+  ]
+}
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.cc
similarity index 99%
rename from chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc
rename to components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.cc
index c0805bf..de37d4d 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h"
 
 #include <algorithm>
 #include <limits>
@@ -108,7 +108,9 @@
 std::string GetHeavyAdReportMessage(const ad_metrics::FrameTreeData& frame_data,
                                     bool will_unload_adframe) {
   const char kChromeStatusMessage[] =
-      "See https://www.chromestatus.com/feature/4800491902992384?utm_source=devtools";
+      "See "
+      "https://www.chromestatus.com/feature/"
+      "4800491902992384?utm_source=devtools";
   const char kReportingOnlyMessage[] =
       "A future version of Chrome may remove this ad";
   const char kInterventionMessage[] = "Ad was removed";
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h b/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h
similarity index 96%
rename from chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h
rename to components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h
index 8acfd03..af85f86 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h
+++ b/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_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 CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_AD_METRICS_ADS_PAGE_LOAD_METRICS_OBSERVER_H_
-#define CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_AD_METRICS_ADS_PAGE_LOAD_METRICS_OBSERVER_H_
+#ifndef COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_AD_METRICS_ADS_PAGE_LOAD_METRICS_OBSERVER_H_
+#define COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_AD_METRICS_ADS_PAGE_LOAD_METRICS_OBSERVER_H_
 
 #include <bitset>
 #include <list>
@@ -17,9 +17,9 @@
 #include "base/scoped_observer.h"
 #include "base/time/tick_clock.h"
 #include "build/build_config.h"
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.h"
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker.h"
 #include "components/blocklist/opt_out_blocklist/opt_out_blocklist_data.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/frame_data.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.h"
 #include "components/page_load_metrics/browser/page_load_metrics_observer.h"
 #include "components/page_load_metrics/common/page_load_metrics.mojom-forward.h"
 #include "components/subresource_filter/content/browser/subresource_filter_observer.h"
@@ -341,4 +341,4 @@
   DISALLOW_COPY_AND_ASSIGN(AdsPageLoadMetricsObserver);
 };
 
-#endif  // CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_AD_METRICS_ADS_PAGE_LOAD_METRICS_OBSERVER_H_
+#endif  // COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_AD_METRICS_ADS_PAGE_LOAD_METRICS_OBSERVER_H_
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc b/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
similarity index 98%
rename from chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
rename to components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
index b2de4e4..6311687 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
+++ b/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h"
 
 #include <map>
 #include <memory>
@@ -21,14 +21,14 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/default_clock.h"
 #include "base/time/time.h"
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.h"
-#include "chrome/test/base/testing_profile.h"
 #include "components/blocklist/opt_out_blocklist/opt_out_blocklist_data.h"
 #include "components/blocklist/opt_out_blocklist/opt_out_blocklist_delegate.h"
 #include "components/blocklist/opt_out_blocklist/opt_out_store.h"
 #include "components/heavy_ad_intervention/heavy_ad_blocklist.h"
 #include "components/heavy_ad_intervention/heavy_ad_features.h"
+#include "components/page_load_metrics/browser/metrics_navigation_throttle.h"
 #include "components/page_load_metrics/browser/metrics_web_contents_observer.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/frame_data.h"
 #include "components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.h"
 #include "components/page_load_metrics/browser/page_load_metrics_memory_tracker.h"
 #include "components/page_load_metrics/browser/page_load_metrics_observer.h"
@@ -399,7 +399,6 @@
   // blink::mojom::LocalFrame
   void SendInterventionReport(const std::string& id,
                               const std::string& message) override {
-
     if (id.empty()) {
       if (!on_empty_report_callback_)
         return;
@@ -804,14 +803,19 @@
   }
 
  private:
-  // SubresourceFilterTestHarness:
-  // TODO(crbug.com/1116095): Remove these methods altogether when this test is
-  // componentized.
-  bool DisableSettingPrefServiceInUserPrefs() override { return true; }
-  bool DisableAddingNavigationThrottles() override { return true; }
-
   const std::string& application_locale() { return application_locale_; }
 
+  // SubresourceFilterTestHarness::
+  void AppendCustomNavigationThrottles(
+      content::NavigationHandle* navigation_handle,
+      std::vector<std::unique_ptr<content::NavigationThrottle>>* throttles)
+      override {
+    if (navigation_handle->IsInMainFrame()) {
+      throttles->push_back(page_load_metrics::MetricsNavigationThrottle::Create(
+          navigation_handle));
+    }
+  }
+
   void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) {
     auto observer = std::make_unique<AdsPageLoadMetricsObserver>(
         /*heavy_ad_service=*/nullptr,
@@ -833,14 +837,6 @@
     }
   }
 
-  // TODO(crbug.com/1116095): Eliminate this override once this test is
-  // componentized; it's present only to satisfy a unit_tests DCHECK that all
-  // BrowserContexts are subclasses of Profile.
-  // content::RenderViewHostTestHarness:
-  std::unique_ptr<content::BrowserContext> CreateBrowserContext() final {
-    return TestingProfile::Builder().Build();
-  }
-
   std::string application_locale_ = "en-US";
   std::unique_ptr<HeavyAdBlocklist> test_blocklist_;
   base::HistogramTester histogram_tester_;
@@ -2080,7 +2076,9 @@
 
   const char kInterventionMessage[] =
       "Ad was removed because its network usage exceeded the limit. "
-      "See https://www.chromestatus.com/feature/4800491902992384?utm_source=devtools";
+      "See "
+      "https://www.chromestatus.com/feature/"
+      "4800491902992384?utm_source=devtools";
   EXPECT_TRUE(HasInterventionReportsAfterFlush(ad_frame));
   EXPECT_EQ(kInterventionMessage, PopLastInterventionReportMessage());
 
@@ -2311,7 +2309,9 @@
   const char kReportOnlyMessage[] =
       "Ad was removed because its "
       "total CPU usage exceeded the limit. "
-      "See https://www.chromestatus.com/feature/4800491902992384?utm_source=devtools";
+      "See "
+      "https://www.chromestatus.com/feature/"
+      "4800491902992384?utm_source=devtools";
   EXPECT_TRUE(HasInterventionReportsAfterFlush(ad_frame));
   histogram_tester().ExpectUniqueSample(
       SuffixedHistogram("HeavyAds.InterventionType2"),
@@ -2789,7 +2789,9 @@
   const char kReportOnlyMessage[] =
       "A future version of Chrome may remove this ad because its network "
       "usage exceeded the limit. "
-      "See https://www.chromestatus.com/feature/4800491902992384?utm_source=devtools";
+      "See "
+      "https://www.chromestatus.com/feature/"
+      "4800491902992384?utm_source=devtools";
 
   EXPECT_TRUE(HasInterventionReportsAfterFlush(ad_frame));
   EXPECT_EQ(kReportOnlyMessage, PopLastInterventionReportMessage());
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.cc b/components/page_load_metrics/browser/observers/ad_metrics/frame_data.cc
similarity index 98%
rename from chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.cc
rename to components/page_load_metrics/browser/observers/ad_metrics/frame_data.cc
index a4c17e7..01f172cc 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.cc
+++ b/components/page_load_metrics/browser/observers/ad_metrics/frame_data.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/frame_data.h"
 
 #include <algorithm>
 #include <limits>
@@ -10,8 +10,8 @@
 
 #include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.h"
 #include "components/heavy_ad_intervention/heavy_ad_features.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "net/base/mime_util.h"
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.h b/components/page_load_metrics/browser/observers/ad_metrics/frame_data.h
similarity index 98%
rename from chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.h
rename to components/page_load_metrics/browser/observers/ad_metrics/frame_data.h
index 6c8029ee..332b0e18 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.h
+++ b/components/page_load_metrics/browser/observers/ad_metrics/frame_data.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_AD_METRICS_FRAME_DATA_H_
-#define CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_AD_METRICS_FRAME_DATA_H_
+#ifndef COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_AD_METRICS_FRAME_DATA_H_
+#define COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_AD_METRICS_FRAME_DATA_H_
 
 // TODO(crbug.com/1136068): Split this file up so that its various classes and
 // enums are in different files as well.
@@ -605,4 +605,4 @@
 
 }  // namespace ad_metrics
 
-#endif  // CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_AD_METRICS_FRAME_DATA_H_
+#endif  // COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_AD_METRICS_FRAME_DATA_H_
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker.cc b/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.cc
similarity index 98%
rename from chrome/browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker.cc
rename to components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.cc
index 4daffc7..d5ae09b 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker.cc
+++ b/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/checked_math.h"
 #include "base/optional.h"
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker.h b/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.h
similarity index 92%
rename from chrome/browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker.h
rename to components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.h
index b2f013d..789a868 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker.h
+++ b/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_AD_METRICS_PAGE_AD_DENSITY_TRACKER_H_
-#define CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_AD_METRICS_PAGE_AD_DENSITY_TRACKER_H_
+#ifndef COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_AD_METRICS_PAGE_AD_DENSITY_TRACKER_H_
+#define COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_AD_METRICS_PAGE_AD_DENSITY_TRACKER_H_
 
 #include <set>
 #include <unordered_map>
@@ -93,4 +93,4 @@
   base::Optional<gfx::Rect> last_main_frame_size_;
 };
 
-#endif  // CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_AD_METRICS_PAGE_AD_DENSITY_TRACKER_H_"
+#endif  // COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_AD_METRICS_PAGE_AD_DENSITY_TRACKER_H_"
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker_unittest.cc b/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker_unittest.cc
similarity index 97%
rename from chrome/browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker_unittest.cc
rename to components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker_unittest.cc
index 65d92c7..d2397657 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker_unittest.cc
+++ b/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker_unittest.cc
@@ -4,7 +4,7 @@
 
 #include <limits>
 
-#include "chrome/browser/page_load_metrics/observers/ad_metrics/page_ad_density_tracker.h"
+#include "components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/geometry/rect.h"
 
diff --git a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
index 226e7250..9c2e4c3 100644
--- a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
+++ b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
@@ -14,7 +14,10 @@
 #include "components/page_load_metrics/browser/layout_shift_normalization.h"
 #include "components/page_load_metrics/browser/page_load_metrics_observer.h"
 #include "components/page_load_metrics/common/page_load_metrics.mojom.h"
-#include "third_party/blink/public/common/mobile_metrics/mobile_friendliness.h"
+
+namespace blink {
+struct MobileFriendliness;
+}  // namespace blink
 
 namespace content {
 class NavigationHandle;
diff --git a/components/password_manager/ios/BUILD.gn b/components/password_manager/ios/BUILD.gn
index e22a07d9..9cdd83f 100644
--- a/components/password_manager/ios/BUILD.gn
+++ b/components/password_manager/ios/BUILD.gn
@@ -32,6 +32,7 @@
     "js_password_manager.mm",
     "password_form_helper.h",
     "password_form_helper.mm",
+    "password_generation_provider.h",
     "password_manager_client_bridge.h",
     "password_manager_driver_bridge.h",
     "password_manager_ios_util.h",
diff --git a/components/password_manager/ios/OWNERS b/components/password_manager/ios/OWNERS
new file mode 100644
index 0000000..4bb66a8
--- /dev/null
+++ b/components/password_manager/ios/OWNERS
@@ -0,0 +1 @@
+kazinova@google.com
diff --git a/components/password_manager/ios/password_generation_provider.h b/components/password_manager/ios/password_generation_provider.h
new file mode 100644
index 0000000..990f521
--- /dev/null
+++ b/components/password_manager/ios/password_generation_provider.h
@@ -0,0 +1,15 @@
+// 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 COMPONENTS_PASSWORD_MANAGER_IOS_PASSWORD_GENERATION_PROVIDER_H_
+#define COMPONENTS_PASSWORD_MANAGER_IOS_PASSWORD_GENERATION_PROVIDER_H_
+
+@protocol PasswordGenerationProvider <NSObject>
+
+// Triggers password generation on the active field.
+- (void)triggerPasswordGeneration;
+
+@end
+
+#endif  // COMPONENTS_PASSWORD_MANAGER_IOS_PASSWORD_GENERATION_PROVIDER_H_
diff --git a/components/password_manager/ios/shared_password_controller.h b/components/password_manager/ios/shared_password_controller.h
index 2762a3c2..eed7c63 100644
--- a/components/password_manager/ios/shared_password_controller.h
+++ b/components/password_manager/ios/shared_password_controller.h
@@ -12,6 +12,7 @@
 #import "components/autofill/ios/form_util/form_activity_observer.h"
 #include "components/password_manager/core/browser/password_manager_interface.h"
 #import "components/password_manager/ios/password_form_helper.h"
+#import "components/password_manager/ios/password_generation_provider.h"
 #import "components/password_manager/ios/password_manager_driver_bridge.h"
 #import "components/password_manager/ios/password_suggestion_helper.h"
 #import "ios/web/public/web_state_observer_bridge.h"
@@ -54,11 +55,12 @@
 // suggestions, filling forms, and generating passwords.
 @interface SharedPasswordController
     : NSObject <CRWWebStateObserver,
-                PasswordManagerDriverBridge,
-                PasswordSuggestionHelperDelegate,
-                PasswordFormHelperDelegate,
+                FormActivityObserver,
                 FormSuggestionProvider,
-                FormActivityObserver>
+                PasswordFormHelperDelegate,
+                PasswordGenerationProvider,
+                PasswordManagerDriverBridge,
+                PasswordSuggestionHelperDelegate>
 
 // Helper contains common password form processing logic.
 @property(nonatomic, readonly) PasswordFormHelper* formHelper;
diff --git a/components/password_manager/ios/shared_password_controller.mm b/components/password_manager/ios/shared_password_controller.mm
index 69840a1..1936f0cc 100644
--- a/components/password_manager/ios/shared_password_controller.mm
+++ b/components/password_manager/ios/shared_password_controller.mm
@@ -52,27 +52,27 @@
 #error "This file requires ARC support."
 #endif
 
+using autofill::FieldRendererId;
 using autofill::FormActivityObserverBridge;
 using autofill::FormData;
-using autofill::PasswordFormGenerationData;
 using autofill::FormRendererId;
-using autofill::FieldRendererId;
+using autofill::PasswordFormGenerationData;
 using base::SysNSStringToUTF16;
-using base::SysUTF8ToNSString;
 using base::SysUTF16ToNSString;
+using base::SysUTF8ToNSString;
 using l10n_util::GetNSString;
 using l10n_util::GetNSStringF;
-using password_manager::metrics_util::LogPasswordDropdownShown;
-using password_manager::metrics_util::PasswordDropdownState;
 using password_manager::AccountSelectFillData;
 using password_manager::FillData;
 using password_manager::GetPageURLAndCheckTrustLevel;
 using password_manager::JsonStringToFormData;
+using password_manager::metrics_util::LogPasswordDropdownShown;
+using password_manager::metrics_util::PasswordDropdownState;
 using password_manager::PasswordFormManagerForUI;
 using password_manager::PasswordGenerationFrameHelper;
-using password_manager::PasswordManagerInterface;
 using password_manager::PasswordManagerClient;
 using password_manager::PasswordManagerDriver;
+using password_manager::PasswordManagerInterface;
 using password_manager::SerializePasswordFormFillData;
 
 namespace {
@@ -121,6 +121,12 @@
 
   FieldRendererId _lastTypedfieldIdentifier;
   NSString* _lastTypedValue;
+
+  // Identifier of the last focused form.
+  FormRendererId _lastFocusedFormIdentifier;
+
+  // Identifier of the last focused field.
+  FieldRendererId _lastFocusedFieldIdentifier;
 }
 
 - (instancetype)initWithWebState:(web::WebState*)webState
@@ -157,6 +163,17 @@
   return _delegate.passwordManagerClient->IsIncognito();
 }
 
+#pragma mark - PasswordGenerationProvider
+
+- (void)triggerPasswordGeneration {
+  if (!_lastFocusedFieldIdentifier) {
+    return;
+  }
+  [self generatePasswordForFormId:_lastFocusedFormIdentifier
+                  fieldIdentifier:_lastFocusedFieldIdentifier
+              isManuallyTriggered:YES];
+}
+
 #pragma mark - CRWWebStateObserver
 
 - (void)webState:(web::WebState*)webState
@@ -242,6 +259,8 @@
   _isPasswordGenerated = NO;
   _lastTypedfieldIdentifier = FieldRendererId();
   _lastTypedValue = nil;
+  _lastFocusedFormIdentifier = FormRendererId();
+  _lastFocusedFieldIdentifier = FieldRendererId();
 }
 
 #pragma mark - FormSuggestionProvider
@@ -292,7 +311,8 @@
     _lastTypedfieldIdentifier = formQuery.uniqueFieldID;
     _lastTypedValue = formQuery.typedValue;
 
-    if ([formQuery.type isEqual:@"input"]) {
+    if ([formQuery.type isEqual:@"input"] ||
+        [formQuery.type isEqual:@"keyup"]) {
       [self.formHelper updateFieldDataOnUserInput:formQuery.uniqueFieldID
                                        inputValue:formQuery.typedValue];
 
@@ -388,7 +408,8 @@
       // Don't call completion because current suggestion state should remain
       // whether user injects a generated password or cancels.
       [self generatePasswordForFormId:uniqueFormID
-                      fieldIdentifier:uniqueFieldID];
+                      fieldIdentifier:uniqueFieldID
+                  isManuallyTriggered:NO];
       password_manager::metrics_util::LogPasswordDropdownItemSelected(
           password_manager::metrics_util::PasswordDropdownSelectedOption::
               kGenerate,
@@ -549,9 +570,24 @@
 }
 
 - (void)generatePasswordForFormId:(FormRendererId)formIdentifier
-                  fieldIdentifier:(FieldRendererId)fieldIdentifier {
-  if (![self formForGenerationFromFormID:formIdentifier])
+                  fieldIdentifier:(FieldRendererId)fieldIdentifier
+              isManuallyTriggered:(BOOL)isManuallyTriggered {
+  const autofill::PasswordFormGenerationData* generationData =
+      [self formForGenerationFromFormID:formIdentifier];
+  if (!isManuallyTriggered && !generationData) {
     return;
+  }
+
+  BOOL shouldUpdateGenerationData =
+      !generationData ||
+      generationData->new_password_renderer_id != fieldIdentifier;
+  if (isManuallyTriggered && shouldUpdateGenerationData) {
+    PasswordFormGenerationData generation_data = {
+        .form_renderer_id = formIdentifier,
+        .new_password_renderer_id = fieldIdentifier,
+    };
+    [self formEligibleForGenerationFound:generation_data];
+  }
 
   // TODO(crbug.com/886583): pass correct |max_length|.
   base::string16 generatedPassword =
@@ -630,15 +666,17 @@
   DCHECK_EQ(_webState, webState);
 
   GURL pageURL;
-  if (!GetPageURLAndCheckTrustLevel(webState, &pageURL))
+  if (!GetPageURLAndCheckTrustLevel(webState, &pageURL) || !frame ||
+      !frame->CanCallJavaScriptFunction() || params.input_missing) {
+    _lastFocusedFormIdentifier = FormRendererId();
+    _lastFocusedFieldIdentifier = FieldRendererId();
     return;
+  }
 
-  if (!frame || !frame->CanCallJavaScriptFunction())
-    return;
-
-  // Return early if |params| is not complete.
-  if (params.input_missing)
-    return;
+  if (params.type == "focus") {
+    _lastFocusedFormIdentifier = params.unique_form_id;
+    _lastFocusedFieldIdentifier = params.unique_field_id;
+  }
 
   // If there's a change in password forms on a page, they should be parsed
   // again.
diff --git a/components/password_manager/ios/shared_password_controller_unittest.mm b/components/password_manager/ios/shared_password_controller_unittest.mm
index cee9df8d..917beb6 100644
--- a/components/password_manager/ios/shared_password_controller_unittest.mm
+++ b/components/password_manager/ios/shared_password_controller_unittest.mm
@@ -9,6 +9,7 @@
 #include "components/autofill/core/common/password_form_generation_data.h"
 #import "components/autofill/ios/browser/form_suggestion.h"
 #import "components/autofill/ios/browser/form_suggestion_provider_query.h"
+#include "components/autofill/ios/form_util/form_activity_params.h"
 #include "components/autofill/ios/form_util/unique_id_data_tab_helper.h"
 #include "components/password_manager/core/browser/password_manager_interface.h"
 #include "components/password_manager/core/browser/stub_password_manager_client.h"
@@ -425,6 +426,57 @@
   [delegate_ verify];
 }
 
+// Tests that triggering password generation on the last focused field triggers
+// the generation flow.
+TEST_F(SharedPasswordControllerTest, TriggerPasswordGeneration) {
+  autofill::FormActivityParams params;
+  params.unique_form_id = autofill::FormRendererId(0);
+  params.field_type = "password";
+  params.unique_field_id = autofill::FieldRendererId(1);
+  params.type = "focus";
+  params.input_missing = false;
+
+  web::FakeWebFrame web_frame("frame-id", /*is_main_frame=*/true, GURL());
+
+  [controller_ webState:&web_state_
+      didRegisterFormActivity:params
+                      inFrame:&web_frame];
+
+  [[delegate_ expect] sharedPasswordController:controller_
+                showGeneratedPotentialPassword:[OCMArg isNotNil]
+                               decisionHandler:[OCMArg any]];
+
+  [controller_ triggerPasswordGeneration];
+
+  [delegate_ verify];
+}
+
+// Tests that triggering password generation on the last focused field does not
+// trigger the generation flow if the last reported form activity did not
+// provide valid form and field identifiers.
+TEST_F(SharedPasswordControllerTest, LastFocusedFieldData) {
+  autofill::FormActivityParams params;
+  params.unique_form_id = autofill::FormRendererId(0);
+  params.field_type = "password";
+  params.unique_field_id = autofill::FieldRendererId(1);
+  params.type = "focus";
+  params.input_missing = true;
+
+  web::FakeWebFrame web_frame("frame-id", /*is_main_frame=*/true, GURL());
+
+  [controller_ webState:&web_state_
+      didRegisterFormActivity:params
+                      inFrame:&web_frame];
+
+  [[delegate_ reject] sharedPasswordController:controller_
+                showGeneratedPotentialPassword:[OCMArg isNotNil]
+                               decisionHandler:[OCMArg any]];
+
+  [controller_ triggerPasswordGeneration];
+
+  [delegate_ verify];
+}
+
 // TODO(crbug.com/1097353): Finish unit testing the rest of the public API.
 
 }  // namespace password_manager
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PackageManagerDelegate.java b/components/payments/content/android/java/src/org/chromium/components/payments/PackageManagerDelegate.java
index 63ba64a..b84634c4 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/PackageManagerDelegate.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/PackageManagerDelegate.java
@@ -13,7 +13,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
 
@@ -44,12 +43,8 @@
     @SuppressLint("PackageManagerGetSignatures")
     public PackageInfo getPackageInfoWithSignatures(String packageName) {
         try {
-            // GET_SIGNATURES is deprecated in API level 28. See:
-            // https://developer.android.com/reference/android/content/pm/PackageManager#GET_SIGNATURES
-            int flag = Build.VERSION.SDK_INT >= 28 ? PackageManager.GET_SIGNING_CERTIFICATES
-                                                   : PackageManager.GET_SIGNATURES;
             return ContextUtils.getApplicationContext().getPackageManager().getPackageInfo(
-                    packageName, flag);
+                    packageName, PackageManager.GET_SIGNATURES);
         } catch (NameNotFoundException e) {
             return null;
         }
diff --git a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogController.java b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogController.java
index 22b0032..9a283c2 100644
--- a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogController.java
+++ b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogController.java
@@ -292,11 +292,12 @@
             // schedule the next dialog.
             if (mState == State.PROMPT_DENIED) {
                 mDialogDelegate.onCancel();
+                destroyDelegate(ContentSettingValues.BLOCK);
             } else {
                 assert mState == State.PROMPT_OPEN;
                 mDialogDelegate.onDismiss();
+                destroyDelegate(ContentSettingValues.DEFAULT);
             }
-            destroyDelegate(ContentSettingValues.BLOCK);
             scheduleDisplay();
         }
     }
diff --git a/components/policy/BUILD.gn b/components/policy/BUILD.gn
index fe41262b..d52f19c 100644
--- a/components/policy/BUILD.gn
+++ b/components/policy/BUILD.gn
@@ -2,10 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/apple/convert_plist.gni")
 import("//build/config/chrome_build.gni")
 import("//build/config/chromeos/ui_mode.gni")
 import("//build/config/features.gni")
+import("//build/config/mac/base_rules.gni")
 import("//build/config/python.gni")
 import("//build/toolchain/toolchain.gni")
 import("//components/policy/resources/policy_templates.gni")
diff --git a/components/reporting/client/BUILD.gn b/components/reporting/client/BUILD.gn
index 4b5e36c..40df259b 100644
--- a/components/reporting/client/BUILD.gn
+++ b/components/reporting/client/BUILD.gn
@@ -94,6 +94,7 @@
     "//components/reporting/storage:test_support",
     "//components/reporting/util:status",
     "//components/reporting/util:status_macros",
+    "//components/reporting/util:test_callbacks_support",
     "//testing/gmock",
     "//testing/gtest",
   ]
diff --git a/components/reporting/client/report_queue_provider_unttest.cc b/components/reporting/client/report_queue_provider_unttest.cc
index ffd8eca..a64f02b2 100644
--- a/components/reporting/client/report_queue_provider_unttest.cc
+++ b/components/reporting/client/report_queue_provider_unttest.cc
@@ -18,6 +18,7 @@
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -30,43 +31,6 @@
 namespace reporting {
 namespace {
 
-// Usage(in tests only):
-//   TestEvent<ResType> e;
-//   ... Do some async work passing e.cb() as a completion callback of
-//       base::OnceCallback<void(ResType* res)> type which also may
-//       perform some other action specified by |done| callback provided
-//       by the caller.
-//   ... = e.result();  // Will wait for e.cb() to be called and return
-//   the
-//       collected result.
-//
-template <typename ResType>
-class TestEvent {
- public:
-  TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
-  ~TestEvent() { EXPECT_FALSE(run_loop_->running()) << "Not responded"; }
-  TestEvent(const TestEvent& other) = delete;
-  TestEvent& operator=(const TestEvent& other) = delete;
-  ResType result() {
-    run_loop_->Run();
-    return std::forward<ResType>(result_);
-  }
-
-  // Completion callback to hand over to the processing method.
-  base::OnceCallback<void(ResType res)> cb() {
-    return base::BindOnce(
-        [](base::RunLoop* run_loop, ResType* result, ResType res) {
-          *result = std::forward<ResType>(res);
-          run_loop->Quit();
-        },
-        base::Unretained(run_loop_.get()), base::Unretained(&result_));
-  }
-
- private:
-  std::unique_ptr<base::RunLoop> run_loop_;
-  ResType result_;
-};
-
 class MockReportQueueProvider : public ReportQueueProvider {
  public:
   InitializingContext* InstantiateInitializingContext(
@@ -150,7 +114,7 @@
   ASSERT_OK(config_result);
   // Use it to asynchronously create ReportingQueue and then asynchronously
   // send the message.
-  TestEvent<Status> e;
+  test::TestEvent<Status> e;
   base::ThreadPool::PostTask(
       FROM_HERE,
       base::BindOnce(
diff --git a/components/reporting/client/report_queue_unittest.cc b/components/reporting/client/report_queue_unittest.cc
index 7d551f3..c888689a 100644
--- a/components/reporting/client/report_queue_unittest.cc
+++ b/components/reporting/client/report_queue_unittest.cc
@@ -12,6 +12,7 @@
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -22,42 +23,6 @@
 namespace reporting {
 namespace {
 
-// Usage (in tests only):
-//
-//   TestEvent<ResType> e;
-//   ... Do some async work passing e.cb() as a completion callback of
-//       base::OnceCallback<void(ResType* res)> type which also may perform some
-//       other action specified by |done| callback provided by the caller.
-//   ... = e.result();  // Will wait for e.cb() to be called and return the
-//       collected result.
-//
-template <typename ResType>
-class TestEvent {
- public:
-  TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
-  ~TestEvent() { EXPECT_FALSE(run_loop_->running()) << "Not responded"; }
-  TestEvent(const TestEvent& other) = delete;
-  TestEvent& operator=(const TestEvent& other) = delete;
-  ResType result() {
-    run_loop_->Run();
-    return std::forward<ResType>(result_);
-  }
-
-  // Completion callback to hand over to the processing method.
-  base::OnceCallback<void(ResType res)> cb() {
-    return base::BindOnce(
-        [](base::RunLoop* run_loop, ResType* result, ResType res) {
-          *result = std::forward<ResType>(res);
-          run_loop->Quit();
-        },
-        base::Unretained(run_loop_.get()), base::Unretained(&result_));
-  }
-
- private:
-  std::unique_ptr<base::RunLoop> run_loop_;
-  ResType result_;
-};
-
 class ReportQueueTest : public ::testing::Test {
  protected:
   ReportQueueTest() = default;
@@ -72,7 +37,7 @@
       .WillOnce(WithArg<2>(Invoke([](ReportQueue::EnqueueCallback cb) {
         std::move(cb).Run(Status::StatusOK());
       })));
-  TestEvent<Status> e;
+  test::TestEvent<Status> e;
   queue.Enqueue("Record", FAST_BATCH, e.cb());
   ASSERT_OK(e.result());
 }
diff --git a/components/reporting/encryption/BUILD.gn b/components/reporting/encryption/BUILD.gn
index c1b13212..b99f641 100644
--- a/components/reporting/encryption/BUILD.gn
+++ b/components/reporting/encryption/BUILD.gn
@@ -88,6 +88,7 @@
   testonly = true
   sources = [
     "encryption_module_unittest.cc",
+    "encryption_unittest.cc",
     "verification_unittest.cc",
   ]
   deps = [
@@ -101,6 +102,7 @@
     "//components/reporting/proto:record_proto",
     "//components/reporting/util:status",
     "//components/reporting/util:status_macros",
+    "//components/reporting/util:test_callbacks_support",
     "//testing/gmock",
     "//testing/gtest",
     "//third_party/boringssl:boringssl",
diff --git a/components/reporting/encryption/encryption_module_unittest.cc b/components/reporting/encryption/encryption_module_unittest.cc
index d5c95e9..b90b92bc 100644
--- a/components/reporting/encryption/encryption_module_unittest.cc
+++ b/components/reporting/encryption/encryption_module_unittest.cc
@@ -19,6 +19,7 @@
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/boringssl/src/include/openssl/curve25519.h"
@@ -26,42 +27,6 @@
 namespace reporting {
 namespace {
 
-// Usage (in tests only):
-//
-//   TestEvent<ResType> e;
-//   ... Do some async work passing e.cb() as a completion callback of
-//       base::OnceCallback<void(ResType* res)> type which also may perform some
-//       other action specified by |done| callback provided by the caller.
-//   ... = e.result();  // Will wait for e.cb() to be called and return the
-//       collected result.
-//
-template <typename ResType>
-class TestEvent {
- public:
-  TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
-  ~TestEvent() { EXPECT_FALSE(run_loop_->running()) << "Not responded"; }
-  TestEvent(const TestEvent& other) = delete;
-  TestEvent& operator=(const TestEvent& other) = delete;
-  ResType result() {
-    run_loop_->Run();
-    return std::forward<ResType>(result_);
-  }
-
-  // Completion callback to hand over to the processing method.
-  base::OnceCallback<void(ResType res)> cb() {
-    return base::BindOnce(
-        [](base::RunLoop* run_loop, ResType* result, ResType res) {
-          *result = std::forward<ResType>(res);
-          run_loop->Quit();
-        },
-        base::Unretained(run_loop_.get()), base::Unretained(&result_));
-  }
-
- private:
-  std::unique_ptr<base::RunLoop> run_loop_;
-  ResType result_;
-};
-
 class EncryptionModuleTest : public ::testing::Test {
  protected:
   EncryptionModuleTest() = default;
@@ -79,7 +44,7 @@
   }
 
   StatusOr<EncryptedRecord> EncryptSync(base::StringPiece data) {
-    TestEvent<StatusOr<EncryptedRecord>> encrypt_record;
+    test::TestEvent<StatusOr<EncryptedRecord>> encrypt_record;
     encryption_module_->EncryptRecord(data, encrypt_record.cb());
     return encrypt_record.result();
   }
@@ -87,18 +52,18 @@
   StatusOr<std::string> DecryptSync(
       std::pair<std::string /*shared_secret*/, std::string /*encrypted_data*/>
           encrypted) {
-    TestEvent<StatusOr<Decryptor::Handle*>> open_decrypt;
+    test::TestEvent<StatusOr<Decryptor::Handle*>> open_decrypt;
     decryptor_->OpenRecord(encrypted.first, open_decrypt.cb());
     auto open_decrypt_result = open_decrypt.result();
     RETURN_IF_ERROR(open_decrypt_result.status());
     Decryptor::Handle* const dec_handle = open_decrypt_result.ValueOrDie();
 
-    TestEvent<Status> add_decrypt;
+    test::TestEvent<Status> add_decrypt;
     dec_handle->AddToRecord(encrypted.second, add_decrypt.cb());
     RETURN_IF_ERROR(add_decrypt.result());
 
     std::string decrypted_string;
-    TestEvent<Status> close_decrypt;
+    test::TestEvent<Status> close_decrypt;
     dec_handle->CloseRecord(base::BindOnce(
         [](std::string* decrypted_string,
            base::OnceCallback<void(Status)> close_cb,
@@ -119,7 +84,7 @@
       Encryptor::PublicKeyId public_key_id,
       base::StringPiece encrypted_key) {
     // Retrieve private key that matches public key hash.
-    TestEvent<StatusOr<std::string>> retrieve_private_key;
+    test::TestEvent<StatusOr<std::string>> retrieve_private_key;
     decryptor_->RetrieveMatchingPrivateKey(public_key_id,
                                            retrieve_private_key.cb());
     ASSIGN_OR_RETURN(std::string private_key, retrieve_private_key.result());
@@ -135,7 +100,7 @@
     uint8_t out_private_key[X25519_PRIVATE_KEY_LEN];
     X25519_keypair(out_public_value, out_private_key);
 
-    TestEvent<StatusOr<Encryptor::PublicKeyId>> record_keys;
+    test::TestEvent<StatusOr<Encryptor::PublicKeyId>> record_keys;
     decryptor_->RecordKeyPair(
         std::string(reinterpret_cast<const char*>(out_private_key),
                     X25519_PRIVATE_KEY_LEN),
@@ -144,7 +109,7 @@
         record_keys.cb());
     ASSIGN_OR_RETURN(Encryptor::PublicKeyId new_public_key_id,
                      record_keys.result());
-    TestEvent<Status> set_public_key;
+    test::TestEvent<Status> set_public_key;
     encryption_module_->UpdateAsymmetricKey(
         std::string(reinterpret_cast<const char*>(out_public_value),
                     X25519_PUBLIC_VALUE_LEN),
@@ -518,7 +483,7 @@
   }
 
   // Register all key pairs for decryption.
-  std::vector<TestEvent<StatusOr<Encryptor::PublicKeyId>>> record_results(
+  std::vector<test::TestEvent<StatusOr<Encryptor::PublicKeyId>>> record_results(
       public_value_strings.size());
   for (size_t i = 0; i < public_value_strings.size(); ++i) {
     base::ThreadPool::PostTask(
@@ -543,7 +508,7 @@
   }
 
   // Encrypt all records in parallel.
-  std::vector<TestEvent<StatusOr<EncryptedRecord>>> results(
+  std::vector<test::TestEvent<StatusOr<EncryptedRecord>>> results(
       kTestStrings.size());
   for (size_t i = 0; i < kTestStrings.size(); ++i) {
     // Choose random key pair.
@@ -555,7 +520,7 @@
   }
 
   // Decrypt all records in parallel.
-  std::vector<TestEvent<StatusOr<std::string>>> decryption_results(
+  std::vector<test::TestEvent<StatusOr<std::string>>> decryption_results(
       kTestStrings.size());
   for (size_t i = 0; i < results.size(); ++i) {
     // Verify encryption success.
diff --git a/components/reporting/encryption/encryption_unittest.cc b/components/reporting/encryption/encryption_unittest.cc
index 061c11d..2d038e0 100644
--- a/components/reporting/encryption/encryption_unittest.cc
+++ b/components/reporting/encryption/encryption_unittest.cc
@@ -17,6 +17,7 @@
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/boringssl/src/include/openssl/curve25519.h"
@@ -24,42 +25,6 @@
 namespace reporting {
 namespace {
 
-// Usage (in tests only):
-//
-//   TestEvent<ResType> e;
-//   ... Do some async work passing e.cb() as a completion callback of
-//       base::OnceCallback<void(ResType* res)> type which also may perform some
-//       other action specified by |done| callback provided by the caller.
-//   ... = e.result();  // Will wait for e.cb() to be called and return the
-//       collected result.
-//
-template <typename ResType>
-class TestEvent {
- public:
-  TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
-  ~TestEvent() { EXPECT_FALSE(run_loop_->running()) << "Not responded"; }
-  TestEvent(const TestEvent& other) = delete;
-  TestEvent& operator=(const TestEvent& other) = delete;
-  ResType result() {
-    run_loop_->Run();
-    return std::forward<ResType>(result_);
-  }
-
-  // Completion callback to hand over to the processing method.
-  base::OnceCallback<void(ResType res)> cb() {
-    return base::BindOnce(
-        [](base::RunLoop* run_loop, ResType* result, ResType res) {
-          *result = std::forward<ResType>(res);
-          run_loop->Quit();
-        },
-        base::Unretained(run_loop_.get()), base::Unretained(&result_));
-  }
-
- private:
-  std::unique_ptr<base::RunLoop> run_loop_;
-  ResType result_;
-};
-
 class EncryptionTest : public ::testing::Test {
  protected:
   EncryptionTest() = default;
@@ -75,18 +40,18 @@
   }
 
   StatusOr<EncryptedRecord> EncryptSync(base::StringPiece data) {
-    TestEvent<StatusOr<Encryptor::Handle*>> open_encrypt;
+    test::TestEvent<StatusOr<Encryptor::Handle*>> open_encrypt;
     encryptor_->OpenRecord(open_encrypt.cb());
     auto open_encrypt_result = open_encrypt.result();
     RETURN_IF_ERROR(open_encrypt_result.status());
     Encryptor::Handle* const enc_handle = open_encrypt_result.ValueOrDie();
 
-    TestEvent<Status> add_encrypt;
+    test::TestEvent<Status> add_encrypt;
     enc_handle->AddToRecord(data, add_encrypt.cb());
     RETURN_IF_ERROR(add_encrypt.result());
 
     EncryptedRecord encrypted;
-    TestEvent<Status> close_encrypt;
+    test::TestEvent<Status> close_encrypt;
     enc_handle->CloseRecord(base::BindOnce(
         [](EncryptedRecord* encrypted,
            base::OnceCallback<void(Status)> close_cb,
@@ -106,18 +71,18 @@
   StatusOr<std::string> DecryptSync(
       std::pair<std::string /*shared_secret*/, std::string /*encrypted_data*/>
           encrypted) {
-    TestEvent<StatusOr<Decryptor::Handle*>> open_decrypt;
+    test::TestEvent<StatusOr<Decryptor::Handle*>> open_decrypt;
     decryptor_->OpenRecord(encrypted.first, open_decrypt.cb());
     auto open_decrypt_result = open_decrypt.result();
     RETURN_IF_ERROR(open_decrypt_result.status());
     Decryptor::Handle* const dec_handle = open_decrypt_result.ValueOrDie();
 
-    TestEvent<Status> add_decrypt;
+    test::TestEvent<Status> add_decrypt;
     dec_handle->AddToRecord(encrypted.second, add_decrypt.cb());
     RETURN_IF_ERROR(add_decrypt.result());
 
     std::string decrypted_string;
-    TestEvent<Status> close_decrypt;
+    test::TestEvent<Status> close_decrypt;
     dec_handle->CloseRecord(base::BindOnce(
         [](std::string* decrypted_string,
            base::OnceCallback<void(Status)> close_cb,
@@ -138,7 +103,7 @@
       Encryptor::PublicKeyId public_key_id,
       base::StringPiece encrypted_key) {
     // Retrieve private key that matches public key hash.
-    TestEvent<StatusOr<std::string>> retrieve_private_key;
+    test::TestEvent<StatusOr<std::string>> retrieve_private_key;
     decryptor_->RetrieveMatchingPrivateKey(public_key_id,
                                            retrieve_private_key.cb());
     ASSIGN_OR_RETURN(std::string private_key, retrieve_private_key.result());
@@ -154,7 +119,7 @@
     uint8_t out_private_key[X25519_PRIVATE_KEY_LEN];
     X25519_keypair(out_public_value, out_private_key);
 
-    TestEvent<StatusOr<Encryptor::PublicKeyId>> record_keys;
+    test::TestEvent<StatusOr<Encryptor::PublicKeyId>> record_keys;
     decryptor_->RecordKeyPair(
         std::string(reinterpret_cast<const char*>(out_private_key),
                     X25519_PRIVATE_KEY_LEN),
@@ -163,7 +128,7 @@
         record_keys.cb());
     ASSIGN_OR_RETURN(Encryptor::PublicKeyId new_public_key_id,
                      record_keys.result());
-    TestEvent<Status> set_public_key;
+    test::TestEvent<Status> set_public_key;
     encryptor_->UpdateAsymmetricKey(
         std::string(reinterpret_cast<const char*>(out_public_value),
                     X25519_PUBLIC_VALUE_LEN),
@@ -513,7 +478,7 @@
   }
 
   // Register all key pairs for decryption.
-  std::vector<TestEvent<StatusOr<Encryptor::PublicKeyId>>> record_results(
+  std::vector<test::TestEvent<StatusOr<Encryptor::PublicKeyId>>> record_results(
       public_value_strings.size());
   for (size_t i = 0; i < public_value_strings.size(); ++i) {
     base::ThreadPool::PostTask(
@@ -538,7 +503,7 @@
   }
 
   // Encrypt all records in parallel.
-  std::vector<TestEvent<StatusOr<EncryptedRecord>>> results(
+  std::vector<test::TestEvent<StatusOr<EncryptedRecord>>> results(
       kTestStrings.size());
   for (size_t i = 0; i < kTestStrings.size(); ++i) {
     // Choose random key pair.
@@ -550,7 +515,7 @@
   }
 
   // Decrypt all records in parallel.
-  std::vector<TestEvent<StatusOr<std::string>>> decryption_results(
+  std::vector<test::TestEvent<StatusOr<std::string>>> decryption_results(
       kTestStrings.size());
   for (size_t i = 0; i < results.size(); ++i) {
     // Verify encryption success.
diff --git a/components/reporting/storage/BUILD.gn b/components/reporting/storage/BUILD.gn
index 34001b3..64fca7b 100644
--- a/components/reporting/storage/BUILD.gn
+++ b/components/reporting/storage/BUILD.gn
@@ -165,6 +165,7 @@
     "//components/reporting/storage/resources:resource_interface",
     "//components/reporting/util:status",
     "//components/reporting/util:status_macros",
+    "//components/reporting/util:test_callbacks_support",
     "//crypto",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/components/reporting/storage/resources/BUILD.gn b/components/reporting/storage/resources/BUILD.gn
index 69410be..6d668dc 100644
--- a/components/reporting/storage/resources/BUILD.gn
+++ b/components/reporting/storage/resources/BUILD.gn
@@ -28,6 +28,7 @@
     ":resource_interface",
     "//base",
     "//base/test:test_support",
+    "//components/reporting/util:test_callbacks_support",
     "//testing/gmock",
     "//testing/gtest",
   ]
diff --git a/components/reporting/storage/resources/resource_interface_unittest.cc b/components/reporting/storage/resources/resource_interface_unittest.cc
index 90bddcf..8ba5e04 100644
--- a/components/reporting/storage/resources/resource_interface_unittest.cc
+++ b/components/reporting/storage/resources/resource_interface_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/task/post_task.h"
 #include "base/task_runner.h"
 #include "base/test/task_environment.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -17,36 +18,6 @@
 namespace reporting {
 namespace {
 
-class TestCallbackWaiter {
- public:
-  TestCallbackWaiter() = default;
-  TestCallbackWaiter(const TestCallbackWaiter& other) = delete;
-  TestCallbackWaiter& operator=(const TestCallbackWaiter& other) = delete;
-
-  void Attach() {
-    const size_t old_counter = counter_.fetch_add(1);
-    DCHECK_GT(old_counter, 0u) << "Cannot attach when already being released";
-  }
-
-  void Signal() {
-    const size_t old_counter = counter_.fetch_sub(1);
-    DCHECK_GT(old_counter, 0u) << "Already being released";
-    if (old_counter == 1u) {
-      // Dropped last owner.
-      run_loop_.Quit();
-    }
-  }
-
-  void Wait() {
-    Signal();  // Rid of the constructor's ownership.
-    run_loop_.Run();
-  }
-
- private:
-  std::atomic<size_t> counter_{1};  // Owned by constructor.
-  base::RunLoop run_loop_;
-};
-
 class ResourceInterfaceTest
     : public ::testing::TestWithParam<ResourceInterface*> {
  protected:
@@ -74,7 +45,7 @@
   uint64_t size = resource_interface()->GetTotal();
 
   // Schedule reservations.
-  TestCallbackWaiter reserve_waiter;
+  test::TestCallbackWaiter reserve_waiter;
   while ((size / 2) > 0u) {
     size /= 2;
     reserve_waiter.Attach();
@@ -82,7 +53,7 @@
         FROM_HERE, {base::TaskPriority::BEST_EFFORT},
         base::BindOnce(
             [](size_t size, ResourceInterface* resource_interface,
-               TestCallbackWaiter* waiter) {
+               test::TestCallbackWaiter* waiter) {
               EXPECT_TRUE(resource_interface->Reserve(size));
               waiter->Signal();
             },
@@ -91,14 +62,14 @@
   reserve_waiter.Wait();
 
   // Schedule discards.
-  TestCallbackWaiter discard_waiter;
+  test::TestCallbackWaiter discard_waiter;
   for (; size < resource_interface()->GetTotal(); size *= 2) {
     discard_waiter.Attach();
     base::ThreadPool::PostTask(
         FROM_HERE, {base::TaskPriority::BEST_EFFORT},
         base::BindOnce(
             [](size_t size, ResourceInterface* resource_interface,
-               TestCallbackWaiter* waiter) {
+               test::TestCallbackWaiter* waiter) {
               resource_interface->Discard(size);
               waiter->Signal();
             },
@@ -111,7 +82,7 @@
 
 TEST_P(ResourceInterfaceTest, SimultaneousScopedReservationTest) {
   uint64_t size = resource_interface()->GetTotal();
-  TestCallbackWaiter waiter;
+  test::TestCallbackWaiter waiter;
   while ((size / 2) > 0u) {
     size /= 2;
     waiter.Attach();
@@ -119,7 +90,7 @@
         FROM_HERE, {base::TaskPriority::BEST_EFFORT},
         base::BindOnce(
             [](size_t size, ResourceInterface* resource_interface,
-               TestCallbackWaiter* waiter) {
+               test::TestCallbackWaiter* waiter) {
               { ScopedReservation(size, resource_interface); }
               waiter->Signal();
             },
diff --git a/components/reporting/storage/storage_queue_stress_test.cc b/components/reporting/storage/storage_queue_stress_test.cc
index 68b1a78..59c927c 100644
--- a/components/reporting/storage/storage_queue_stress_test.cc
+++ b/components/reporting/storage/storage_queue_stress_test.cc
@@ -24,6 +24,7 @@
 #include "components/reporting/storage/storage_configuration.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/statusor.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "crypto/sha2.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -45,42 +46,6 @@
 constexpr size_t kTotalWritesPerStart = 16;
 constexpr char kDataPrefix[] = "Rec";
 
-// Usage (in tests only):
-//
-//   TestEvent<ResType> e;
-//   ... Do some async work passing e.cb() as a completion callback of
-//       base::OnceCallback<void(ResType* res)> type which also may perform some
-//       other action specified by |done| callback provided by the caller.
-//   ... = e.result();  // Will wait for e.cb() to be called and return the
-//       collected result.
-//
-template <typename ResType>
-class TestEvent {
- public:
-  TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
-  ~TestEvent() { EXPECT_FALSE(run_loop_->running()) << "Not responded"; }
-  TestEvent(const TestEvent& other) = delete;
-  TestEvent& operator=(const TestEvent& other) = delete;
-  ResType result() {
-    run_loop_->Run();
-    return std::forward<ResType>(result_);
-  }
-
-  // Completion callback to hand over to the processing method.
-  base::OnceCallback<void(ResType res)> cb() {
-    return base::BindOnce(
-        [](base::RunLoop* run_loop, ResType* result, ResType res) {
-          *result = std::forward<ResType>(res);
-          run_loop->Quit();
-        },
-        base::Unretained(run_loop_.get()), base::Unretained(&result_));
-  }
-
- private:
-  std::unique_ptr<base::RunLoop> run_loop_;
-  ResType result_;
-};
-
 class TestUploadClient : public UploaderInterface {
  public:
   // Mapping of <generation id, sequencing id> to matching record digest.
@@ -175,7 +140,7 @@
     ASSERT_FALSE(storage_queue_) << "StorageQueue already assigned";
     test_encryption_module_ =
         base::MakeRefCounted<test::TestEncryptionModule>();
-    TestEvent<StatusOr<scoped_refptr<StorageQueue>>> e;
+    test::TestEvent<StatusOr<scoped_refptr<StorageQueue>>> e;
     StorageQueue::Create(
         options,
         base::BindRepeating(&StorageQueueStressTest::BuildTestUploader,
@@ -234,45 +199,12 @@
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
 };
 
-class TestCallbackWaiter {
- public:
-  TestCallbackWaiter() : runner_(base::ThreadTaskRunnerHandle::Get()) {}
-  TestCallbackWaiter(const TestCallbackWaiter& other) = delete;
-  TestCallbackWaiter& operator=(const TestCallbackWaiter& other) = delete;
-
-  void Attach() {
-    const size_t old_counter = counter_.fetch_add(1);
-    DCHECK_GT(old_counter, 0u) << "Cannot attach when already being released";
-  }
-
-  void Signal() {
-    const size_t old_counter = counter_.fetch_sub(1);
-    DCHECK_GT(old_counter, 0u) << "Already being released";
-    if (old_counter > 1u) {
-      // There are more owners.
-      return;
-    }
-    // Dropping the last owner.
-    run_loop_.Quit();
-  }
-
-  void Wait() {
-    Signal();  // Rid of the constructor's ownership.
-    run_loop_.Run();
-  }
-
- private:
-  std::atomic<size_t> counter_{1};  // Owned by constructor.
-  const scoped_refptr<base::SingleThreadTaskRunner> runner_;
-  base::RunLoop run_loop_;
-};
-
 TEST_P(StorageQueueStressTest,
        WriteIntoNewStorageQueueReopenWriteMoreAndUpload) {
   for (size_t iStart = 0; iStart < kTotalQueueStarts; ++iStart) {
-    TestCallbackWaiter write_waiter;
+    test::TestCallbackWaiter write_waiter;
     base::RepeatingCallback<void(Status)> cb = base::BindRepeating(
-        [](TestCallbackWaiter* waiter, Status status) {
+        [](test::TestCallbackWaiter* waiter, Status status) {
           EXPECT_OK(status);
           waiter->Signal();
         },
diff --git a/components/reporting/storage/storage_queue_unittest.cc b/components/reporting/storage/storage_queue_unittest.cc
index 3336892c..0bc76fe 100644
--- a/components/reporting/storage/storage_queue_unittest.cc
+++ b/components/reporting/storage/storage_queue_unittest.cc
@@ -22,6 +22,7 @@
 #include "components/reporting/storage/storage_configuration.h"
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/statusor.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "crypto/sha2.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -42,42 +43,6 @@
 // Metadata file name prefix.
 const base::FilePath::CharType METADATA_NAME[] = FILE_PATH_LITERAL("META");
 
-// Usage (in tests only):
-//
-//   TestEvent<ResType> e;
-//   ... Do some async work passing e.cb() as a completion callback of
-//       base::OnceCallback<void(ResType* res)> type which also may perform some
-//       other action specified by |done| callback provided by the caller.
-//   ... = e.result();  // Will wait for e.cb() to be called and return the
-//       collected result.
-//
-template <typename ResType>
-class TestEvent {
- public:
-  TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
-  ~TestEvent() { EXPECT_FALSE(run_loop_->running()) << "Not responded"; }
-  TestEvent(const TestEvent& other) = delete;
-  TestEvent& operator=(const TestEvent& other) = delete;
-  ResType result() {
-    run_loop_->Run();
-    return std::forward<ResType>(result_);
-  }
-
-  // Completion callback to hand over to the processing method.
-  base::OnceCallback<void(ResType res)> cb() {
-    return base::BindOnce(
-        [](base::RunLoop* run_loop, ResType* result, ResType res) {
-          *result = std::forward<ResType>(res);
-          run_loop->Quit();
-        },
-        base::Unretained(run_loop_.get()), base::Unretained(&result_));
-  }
-
- private:
-  std::unique_ptr<base::RunLoop> run_loop_;
-  ResType result_;
-};
-
 class MockUploadClient : public ::testing::NiceMock<UploaderInterface> {
  public:
   // Mapping of <generation id, sequencing id> to matching record digest.
@@ -305,7 +270,7 @@
     ASSERT_FALSE(storage_queue_) << "StorageQueue already assigned";
     test_encryption_module_ =
         base::MakeRefCounted<test::TestEncryptionModule>();
-    TestEvent<StatusOr<scoped_refptr<StorageQueue>>> e;
+    test::TestEvent<StatusOr<scoped_refptr<StorageQueue>>> e;
     StorageQueue::Create(
         options,
         base::BindRepeating(&StorageQueueTest::BuildMockUploader,
@@ -350,7 +315,7 @@
 
   Status WriteString(base::StringPiece data) {
     EXPECT_TRUE(storage_queue_) << "StorageQueue not created yet";
-    TestEvent<Status> w;
+    test::TestEvent<Status> w;
     Record record;
     record.set_data(std::string(data));
     record.set_destination(UPLOAD_EVENTS);
@@ -366,7 +331,7 @@
 
   void ConfirmOrDie(base::Optional<std::int64_t> sequencing_id,
                     bool force = false) {
-    TestEvent<Status> c;
+    test::TestEvent<Status> c;
     storage_queue_->Confirm(sequencing_id, force, c.cb());
     const Status c_result = c.result();
     ASSERT_OK(c_result) << c_result;
diff --git a/components/reporting/storage/storage_unittest.cc b/components/reporting/storage/storage_unittest.cc
index 12b9967..2d83ae3 100644
--- a/components/reporting/storage/storage_unittest.cc
+++ b/components/reporting/storage/storage_unittest.cc
@@ -26,6 +26,7 @@
 #include "components/reporting/util/status.h"
 #include "components/reporting/util/status_macros.h"
 #include "components/reporting/util/statusor.h"
+#include "components/reporting/util/test_support_callbacks.h"
 #include "crypto/sha2.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -47,42 +48,6 @@
 namespace reporting {
 namespace {
 
-// Usage (in tests only):
-//
-//   TestEvent<ResType> e;
-//   ... Do some async work passing e.cb() as a completion callback of
-//       base::OnceCallback<void(ResType* res)> type which also may perform some
-//       other action specified by |done| callback provided by the caller.
-//   ... = e.result();  // Will wait for e.cb() to be called and return the
-//       collected result.
-//
-template <typename ResType>
-class TestEvent {
- public:
-  TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
-  ~TestEvent() { EXPECT_FALSE(run_loop_->running()) << "Not responded"; }
-  TestEvent(const TestEvent& other) = delete;
-  TestEvent& operator=(const TestEvent& other) = delete;
-  ResType result() {
-    run_loop_->Run();
-    return std::forward<ResType>(result_);
-  }
-
-  // Completion callback to hand over to the processing method.
-  base::OnceCallback<void(ResType res)> cb() {
-    return base::BindOnce(
-        [](base::RunLoop* run_loop, ResType* result, ResType res) {
-          *result = std::forward<ResType>(res);
-          run_loop->Quit();
-        },
-        base::Unretained(run_loop_.get()), base::Unretained(&result_));
-  }
-
- private:
-  std::unique_ptr<base::RunLoop> run_loop_;
-  ResType result_;
-};
-
 // Context of single decryption. Self-destructs upon completion or failure.
 class SingleDecryptionContext {
  public:
@@ -556,7 +521,7 @@
           .RetiresOnSaturation();
     }
     // Initialize Storage with no key.
-    TestEvent<StatusOr<scoped_refptr<Storage>>> e;
+    test::TestEvent<StatusOr<scoped_refptr<Storage>>> e;
     Storage::Create(options,
                     base::BindRepeating(&StorageTest::BuildMockUploader,
                                         base::Unretained(this)),
@@ -615,7 +580,7 @@
 
   Status WriteString(Priority priority, base::StringPiece data) {
     EXPECT_TRUE(storage_) << "Storage not created yet";
-    TestEvent<Status> w;
+    test::TestEvent<Status> w;
     Record record;
     record.set_data(std::string(data));
     record.set_destination(UPLOAD_EVENTS);
@@ -632,7 +597,7 @@
   void ConfirmOrDie(Priority priority,
                     base::Optional<std::int64_t> sequencing_id,
                     bool force = false) {
-    TestEvent<Status> c;
+    test::TestEvent<Status> c;
     storage_->Confirm(priority, sequencing_id, force, c.cb());
     const Status c_result = c.result();
     ASSERT_OK(c_result) << c_result;
@@ -645,7 +610,7 @@
     Encryptor::PublicKeyId public_key_id;
     uint8_t public_value[X25519_PUBLIC_VALUE_LEN];
     X25519_keypair(public_value, private_key);
-    TestEvent<StatusOr<Encryptor::PublicKeyId>> prepare_key_pair;
+    test::TestEvent<StatusOr<Encryptor::PublicKeyId>> prepare_key_pair;
     decryptor_->RecordKeyPair(
         std::string(reinterpret_cast<const char*>(private_key),
                     X25519_PRIVATE_KEY_LEN),
diff --git a/components/reporting/util/BUILD.gn b/components/reporting/util/BUILD.gn
index 421347fd6..c56cbc92 100644
--- a/components/reporting/util/BUILD.gn
+++ b/components/reporting/util/BUILD.gn
@@ -66,6 +66,15 @@
   deps = [ "//base" ]
 }
 
+source_set("test_callbacks_support") {
+  sources = [
+    "test_support_callbacks.cc",
+    "test_support_callbacks.h",
+  ]
+
+  deps = [ "//base" ]
+}
+
 # All unit tests are built as part of the //components:components_unittests
 # target.
 source_set("unit_tests") {
@@ -84,6 +93,7 @@
     ":status_macros",
     ":status_proto",
     ":task_runner_context",
+    ":test_callbacks_support",
     "//base",
     "//base/test:test_support",
     "//testing/gmock",
diff --git a/components/reporting/util/test_support_callbacks.cc b/components/reporting/util/test_support_callbacks.cc
new file mode 100644
index 0000000..60433ac
--- /dev/null
+++ b/components/reporting/util/test_support_callbacks.cc
@@ -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.
+
+#include "components/reporting/util/test_support_callbacks.h"
+
+#include "base/run_loop.h"
+
+namespace reporting {
+namespace test {
+
+TestCallbackWaiter::TestCallbackWaiter()
+    : run_loop_(base::RunLoop::Type::kNestableTasksAllowed) {}
+TestCallbackWaiter::~TestCallbackWaiter() = default;
+
+}  // namespace test
+}  // namespace reporting
\ No newline at end of file
diff --git a/components/reporting/util/test_support_callbacks.h b/components/reporting/util/test_support_callbacks.h
new file mode 100644
index 0000000..38350967
--- /dev/null
+++ b/components/reporting/util/test_support_callbacks.h
@@ -0,0 +1,135 @@
+// 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 COMPONENTS_REPORTING_UTIL_TEST_SUPPORT_CALLBACKS_H_
+#define COMPONENTS_REPORTING_UTIL_TEST_SUPPORT_CALLBACKS_H_
+
+#include <atomic>
+#include <tuple>
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/run_loop.h"
+
+namespace reporting {
+namespace test {
+
+// Usage (in tests only):
+//
+//   TestEvent<ResType> e;
+//   ... Do some async work passing e.cb() as a completion callback of
+//   base::OnceCallback<void(ResType res)> type which also may perform some
+//   other action specified by |done| callback provided by the caller.
+//   ... = e.result();  // Will wait for e.cb() to be called and return the
+//   collected result.
+//
+template <typename ResType>
+class TestEvent {
+ public:
+  TestEvent() : run_loop_(base::RunLoop::Type::kNestableTasksAllowed) {}
+  ~TestEvent() = default;
+  TestEvent(const TestEvent& other) = delete;
+  TestEvent& operator=(const TestEvent& other) = delete;
+  ResType result() {
+    run_loop_.Run();
+    return std::forward<ResType>(result_);
+  }
+
+  // Repeating callback to hand over to the processing method.
+  // Even though it is repeating, it can be only called once, since
+  // it quits run_loop; repeating declaration is only needed for cases
+  // when the caller requires it.
+  // If the caller expects OnceCallback, result will be converted automatically.
+  base::RepeatingCallback<void(ResType res)> cb() {
+    return base::BindRepeating(
+        [](base::RunLoop* run_loop, ResType* result, ResType res) {
+          *result = std::forward<ResType>(res);
+          run_loop->Quit();
+        },
+        base::Unretained(&run_loop_), base::Unretained(&result_));
+  }
+
+ private:
+  base::RunLoop run_loop_;
+  ResType result_;
+};
+
+// Usage (in tests only):
+//
+//   TestMultiEvent<ResType1, Restype2, ...> e;
+//   ... Do some async work passing e.cb() as a completion callback of
+//   base::OnceCallback<void(ResType1, Restype2, ...)> type which also may
+//   perform some other action specified by |done| callback provided by the
+//   caller. std::tie(res1, res2, ...) = e.result();  // Will wait for e.cb() to
+//   be called and return the collected results.
+//
+template <typename... ResType>
+class TestMultiEvent {
+ public:
+  TestMultiEvent() : run_loop_(base::RunLoop::Type::kNestableTasksAllowed) {}
+  ~TestMultiEvent() = default;
+  TestMultiEvent(const TestMultiEvent& other) = delete;
+  TestMultiEvent& operator=(const TestMultiEvent& other) = delete;
+  std::tuple<ResType...> result() {
+    run_loop_.Run();
+    return std::forward<std::tuple<ResType...>>(result_);
+  }
+
+  // Completion callback to hand over to the processing method.
+  base::RepeatingCallback<void(ResType... res)> cb() {
+    return base::BindRepeating(
+        [](base::RunLoop* run_loop, std::tuple<ResType...>* result,
+           ResType... res) {
+          *result = std::forward_as_tuple(res...);
+          run_loop->Quit();
+        },
+        base::Unretained(&run_loop_), base::Unretained(&result_));
+  }
+
+ private:
+  base::RunLoop run_loop_;
+  std::tuple<ResType...> result_;
+};
+
+// Usage (in tests only):
+//
+//  TestCallbackWaiter waiter;
+
+class TestCallbackWaiter {
+ public:
+  TestCallbackWaiter();
+  ~TestCallbackWaiter();
+  TestCallbackWaiter(const TestCallbackWaiter& other) = delete;
+  TestCallbackWaiter& operator=(const TestCallbackWaiter& other) = delete;
+
+  void Attach(size_t more = 1) {
+    const size_t old_counter = counter_.fetch_add(more);
+    DCHECK_GT(old_counter, 0u) << "Cannot attach when already being released";
+  }
+
+  void Signal() {
+    const size_t old_counter = counter_.fetch_sub(1);
+    DCHECK_GT(old_counter, 0u) << "Already being released";
+    if (old_counter > 1u) {
+      // There are more owners.
+      return;
+    }
+    // Dropping the last owner.
+    run_loop_.Quit();
+  }
+
+  void Wait() {
+    Signal();  // Rid of the constructor's ownership.
+    run_loop_.Run();
+  }
+
+ private:
+  std::atomic<size_t> counter_{1};  // Owned by constructor.
+  base::RunLoop run_loop_;
+};
+
+}  // namespace test
+}  // namespace reporting
+
+#endif  // COMPONENTS_REPORTING_UTIL_TEST_SUPPORT_CALLBACKS_H_
\ No newline at end of file
diff --git a/components/shared_highlighting/core/common/BUILD.gn b/components/shared_highlighting/core/common/BUILD.gn
index 2cd12c45..0cd5cd62 100644
--- a/components/shared_highlighting/core/common/BUILD.gn
+++ b/components/shared_highlighting/core/common/BUILD.gn
@@ -6,8 +6,6 @@
   sources = [
     "disabled_sites.cc",
     "disabled_sites.h",
-    "features.cc",
-    "features.h",
     "shared_highlighting_features.cc",
     "shared_highlighting_features.h",
     "shared_highlighting_metrics.cc",
diff --git a/components/shared_highlighting/core/common/features.cc b/components/shared_highlighting/core/common/features.cc
deleted file mode 100644
index 67aec43d..0000000
--- a/components/shared_highlighting/core/common/features.cc
+++ /dev/null
@@ -1,12 +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 "components/shared_highlighting/core/common/features.h"
-
-namespace features {
-
-// Enables link to text to be generated in advance.
-const base::Feature kPreemptiveLinkToTextGeneration{
-    "PreemptiveLinkToTextGeneration", base::FEATURE_DISABLED_BY_DEFAULT};
-}  // namespace features
\ No newline at end of file
diff --git a/components/shared_highlighting/core/common/features.h b/components/shared_highlighting/core/common/features.h
deleted file mode 100644
index 1d43386a..0000000
--- a/components/shared_highlighting/core/common/features.h
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_SHARED_HIGHLIGHTING_CORE_COMMON_FEATURES_H_
-#define COMPONENTS_SHARED_HIGHLIGHTING_CORE_COMMON_FEATURES_H_
-
-#include "base/feature_list.h"
-#include "build/build_config.h"
-
-namespace features {
-extern const base::Feature kPreemptiveLinkToTextGeneration;
-}
-
-#endif  // COMPONENTS_SHARED_HIGHLIGHTING_CORE_COMMON_FEATURES_H_
\ No newline at end of file
diff --git a/components/shared_highlighting/core/common/shared_highlighting_metrics.cc b/components/shared_highlighting/core/common/shared_highlighting_metrics.cc
index a30e7833..f8723fa 100644
--- a/components/shared_highlighting/core/common/shared_highlighting_metrics.cc
+++ b/components/shared_highlighting/core/common/shared_highlighting_metrics.cc
@@ -56,7 +56,6 @@
   base::UmaHistogramCounts100("TextFragmentAnchor.SelectorCount", count);
 }
 
-// TODO(gayane): Replace by one function LogGenerateError(Error).
 void LogGenerateErrorTabHidden() {
   LogLinkGenerationErrorReason(LinkGenerationError::kTabHidden);
 }
@@ -77,10 +76,6 @@
   LogLinkGenerationErrorReason(LinkGenerationError::kBlockList);
 }
 
-void LogGenerateErrorTimeout() {
-  LogLinkGenerationErrorReason(LinkGenerationError::kTimeout);
-}
-
 void LogGenerateSuccessLatency(base::TimeDelta latency) {
   base::UmaHistogramTimes("SharedHighlights.LinkGenerated.TimeToGenerate",
                           latency);
diff --git a/components/shared_highlighting/core/common/shared_highlighting_metrics.h b/components/shared_highlighting/core/common/shared_highlighting_metrics.h
index ce3ff6f..96ec68f1 100644
--- a/components/shared_highlighting/core/common/shared_highlighting_metrics.h
+++ b/components/shared_highlighting/core/common/shared_highlighting_metrics.h
@@ -94,10 +94,6 @@
 // a blocklisted page.
 void LogGenerateErrorBlockList();
 
-// Records when link generation was not triggered because selection happened on
-// a blocklisted page.
-void LogGenerateErrorTimeout();
-
 // Records the latency for successfully generating a link.
 void LogGenerateSuccessLatency(base::TimeDelta latency);
 
diff --git a/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/IdentityManager.java b/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/IdentityManager.java
index 058488e0..7d0386c 100644
--- a/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/IdentityManager.java
+++ b/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/IdentityManager.java
@@ -5,6 +5,7 @@
 package org.chromium.components.signin.identitymanager;
 
 import android.accounts.Account;
+import android.os.SystemClock;
 
 import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
@@ -13,10 +14,14 @@
 import org.chromium.base.ObserverList;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.NativeMethods;
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.components.signin.base.AccountInfo;
 import org.chromium.components.signin.base.CoreAccountId;
 import org.chromium.components.signin.base.CoreAccountInfo;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * IdentityManager provides access to native IdentityManager's public API to java components.
  */
@@ -56,6 +61,9 @@
     private ProfileOAuth2TokenServiceDelegate mProfileOAuth2TokenServiceDelegate;
 
     private final ObserverList<Observer> mObservers = new ObserverList<>();
+    // Account id and the corresponding fetch start time, this is only used to record the
+    // account information fetch duration.
+    private final Map<CoreAccountId, Long> mAccountAndFetchStartTimes = new HashMap<>();
 
     /**
      * Called by native to create an instance of IdentityManager.
@@ -122,6 +130,14 @@
     @CalledByNative
     @VisibleForTesting
     public void onExtendedAccountInfoUpdated(AccountInfo accountInfo) {
+        final CoreAccountId accountId = accountInfo.getId();
+        if (accountInfo.getAccountImage() != null
+                && mAccountAndFetchStartTimes.containsKey(accountId)) {
+            long startTime = mAccountAndFetchStartTimes.get(accountId);
+            RecordHistogram.recordTimesHistogram("Signin.AndroidAccountInfoFetchTime",
+                    SystemClock.elapsedRealtime() - startTime);
+            mAccountAndFetchStartTimes.remove(accountId);
+        }
         for (Observer observer : mObservers) {
             observer.onExtendedAccountInfoUpdated(accountInfo);
         }
@@ -174,6 +190,7 @@
      */
     public void forceRefreshOfExtendedAccountInfo(CoreAccountId coreAccountId) {
         assert coreAccountId != null : "coreAccountId shouldn't be null!";
+        mAccountAndFetchStartTimes.put(coreAccountId, SystemClock.elapsedRealtime());
         IdentityManagerJni.get().forceRefreshOfExtendedAccountInfo(
                 mNativeIdentityManager, coreAccountId);
     }
diff --git a/components/signin/public/identity_manager/access_token_constants.cc b/components/signin/public/identity_manager/access_token_constants.cc
index 8ce29c7..5fed8cc 100644
--- a/components/signin/public/identity_manager/access_token_constants.cc
+++ b/components/signin/public/identity_manager/access_token_constants.cc
@@ -71,6 +71,7 @@
       GaiaConstants::kCloudTranslationOAuth2Scope,
       GaiaConstants::kDriveOAuth2Scope,
       GaiaConstants::kKidFamilyReadonlyOAuth2Scope,
+      GaiaConstants::kKidManagementOAuth2Scope,
       GaiaConstants::kKidManagementPrivilegedOAuth2Scope,
       GaiaConstants::kKidsSupervisionSetupChildOAuth2Scope,
       GaiaConstants::kNearbyShareOAuth2Scope,
diff --git a/components/strings/BUILD.gn b/components/strings/BUILD.gn
index 15bf6b0..973ae60 100644
--- a/components/strings/BUILD.gn
+++ b/components/strings/BUILD.gn
@@ -37,7 +37,7 @@
   ]
 
   outputs = [ "grit/components_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "components_strings_$locale.pak" ]
   }
 
@@ -58,7 +58,7 @@
 grit("components_chromium_strings") {
   source = "../components_chromium_strings.grd"
   outputs = [ "grit/components_chromium_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "components_chromium_strings_$locale.pak" ]
   }
 }
@@ -66,7 +66,7 @@
 grit("components_google_chrome_strings") {
   source = "../components_google_chrome_strings.grd"
   outputs = [ "grit/components_google_chrome_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "components_google_chrome_strings_$locale.pak" ]
   }
 }
@@ -82,7 +82,7 @@
 grit("components_locale_settings") {
   source = "../components_locale_settings.grd"
   outputs = [ "grit/components_locale_settings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "components_locale_settings_$locale.pak" ]
   }
 
diff --git a/components/subresource_filter/content/browser/subresource_filter_test_harness.cc b/components/subresource_filter/content/browser/subresource_filter_test_harness.cc
index a99c95ed..bfaea14 100644
--- a/components/subresource_filter/content/browser/subresource_filter_test_harness.cc
+++ b/components/subresource_filter/content/browser/subresource_filter_test_harness.cc
@@ -47,8 +47,7 @@
   content::RenderViewHostTestHarness::SetUp();
 
   // Set up prefs-related state needed by various tests.
-  if (!DisableSettingPrefServiceInUserPrefs())
-    user_prefs::UserPrefs::Set(browser_context(), &pref_service_);
+  user_prefs::UserPrefs::Set(browser_context(), &pref_service_);
 
   // Ensure correct features.
   scoped_configuration_.ResetConfiguration(Configuration(
@@ -109,9 +108,6 @@
 // content::WebContentsObserver:
 void SubresourceFilterTestHarness::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
-  if (DisableAddingNavigationThrottles())
-    return;
-
   if (navigation_handle->IsSameDocument())
     return;
 
@@ -119,6 +115,8 @@
   ContentSubresourceFilterThrottleManager::FromWebContents(web_contents())
       ->MaybeAppendNavigationThrottles(navigation_handle, &throttles);
 
+  AppendCustomNavigationThrottles(navigation_handle, &throttles);
+
   for (auto& it : throttles) {
     navigation_handle->RegisterThrottleForTesting(std::move(it));
   }
@@ -169,12 +167,4 @@
       ->SetFrameAsAdSubframeForTesting(render_frame_host);
 }
 
-bool SubresourceFilterTestHarness::DisableSettingPrefServiceInUserPrefs() {
-  return false;
-}
-
-bool SubresourceFilterTestHarness::DisableAddingNavigationThrottles() {
-  return false;
-}
-
 }  // namespace subresource_filter
diff --git a/components/subresource_filter/content/browser/subresource_filter_test_harness.h b/components/subresource_filter/content/browser/subresource_filter_test_harness.h
index 4d177b97..dffc792 100644
--- a/components/subresource_filter/content/browser/subresource_filter_test_harness.h
+++ b/components/subresource_filter/content/browser/subresource_filter_test_harness.h
@@ -19,6 +19,7 @@
 class GURL;
 
 namespace content {
+class NavigationThrottle;
 class RenderFrameHost;
 }  // namespace content
 
@@ -81,14 +82,10 @@
   }
 
  protected:
-  // Tests run as part of unit_tests must override these methods to return true
-  // to avoid crashes, as in that context the PrefService is already set in
-  // UserPrefs and subresource filter navigation throttles are added
-  // automatically.
-  // TODO(crbug.com/1116095): Remove these methods entirely when
-  // the AdsPageLoadMetricsObserver unittest is componentized.
-  virtual bool DisableSettingPrefServiceInUserPrefs();
-  virtual bool DisableAddingNavigationThrottles();
+  // Tests can override this to have custom throttles added to navigations.
+  virtual void AppendCustomNavigationThrottles(
+      content::NavigationHandle* navigation_handle,
+      std::vector<std::unique_ptr<content::NavigationThrottle>>* throttles) {}
 
   sync_preferences::TestingPrefServiceSyncable* pref_service() {
     return &pref_service_;
diff --git a/components/sync/driver/sync_driver_switches.cc b/components/sync/driver/sync_driver_switches.cc
index 61f87c0..5f600e0 100644
--- a/components/sync/driver/sync_driver_switches.cc
+++ b/components/sync/driver/sync_driver_switches.cc
@@ -48,7 +48,7 @@
 
 // Controls whether to enable syncing of Autofill Wallet offer data.
 const base::Feature kSyncAutofillWalletOfferData{
-    "SyncAutofillWalletOfferData", base::FEATURE_DISABLED_BY_DEFAULT};
+    "SyncAutofillWalletOfferData", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Controls whether to enable syncing of Wi-Fi configurations.
 const base::Feature kSyncWifiConfigurations{"SyncWifiConfigurations",
diff --git a/components/sync/protocol/vault.proto b/components/sync/protocol/vault.proto
index 55a3835..adcb452 100644
--- a/components/sync/protocol/vault.proto
+++ b/components/sync/protocol/vault.proto
@@ -9,28 +9,43 @@
 option java_package = "sync_pb";
 package sync_pb;
 
-message SharedKey {
+message SharedMemberKey {
   optional int32 epoch = 1;
   optional bytes wrapped_key = 2;
   optional bytes member_proof = 3;
-  optional bytes key_proof = 4;
+}
+
+message RotationProof {
+  optional int32 new_epoch = 1;
+  optional bytes rotation_proof = 2;
 }
 
 message SecurityDomain {
   optional string name = 1;
+}
 
-  message Member {
-    optional bytes public_key = 1;
-    repeated SharedKey keys = 2;
+message SecurityDomainMember {
+  optional string name = 1;
+  optional bytes public_key = 2;
+
+  message SecurityDomainMembership {
+    optional string security_domain = 1;
+    repeated SharedMemberKey keys = 3;
+    repeated RotationProof rotation_proofs = 4;
   }
 
-  repeated Member members = 2;
+  repeated SecurityDomainMembership memberships = 3;
+
+  enum MemberType {
+    MEMBER_TYPE_UNSPECIFIED = 0;
+    MEMBER_TYPE_PHYSICAL_DEVICE = 1;
+  }
+
+  optional MemberType member_type = 4;
 }
 
 message JoinSecurityDomainsRequest {
-  repeated SecurityDomain security_domains = 1;
-}
-
-message ListSecurityDomainsResponse {
-  repeated SecurityDomain security_domains = 1;
+  optional SecurityDomain security_domain = 1;
+  optional SecurityDomainMember security_domain_member = 2;
+  optional SharedMemberKey shared_member_key = 3;
 }
diff --git a/components/sync/trusted_vault/BUILD.gn b/components/sync/trusted_vault/BUILD.gn
index 3a32459..854dbfa 100644
--- a/components/sync/trusted_vault/BUILD.gn
+++ b/components/sync/trusted_vault/BUILD.gn
@@ -45,3 +45,17 @@
     "//third_party/boringssl",
   ]
 }
+
+static_library("test_support") {
+  testonly = true
+  sources = [
+    "fake_security_domains_server.cc",
+    "fake_security_domains_server.h",
+  ]
+
+  deps = [
+    "//components/sync/protocol",
+    "//components/sync/trusted_vault",
+    "//net:test_support",
+  ]
+}
diff --git a/components/sync/trusted_vault/download_keys_response_handler.cc b/components/sync/trusted_vault/download_keys_response_handler.cc
index 95b8fa84..9c67825 100644
--- a/components/sync/trusted_vault/download_keys_response_handler.cc
+++ b/components/sync/trusted_vault/download_keys_response_handler.cc
@@ -4,7 +4,7 @@
 
 #include "components/sync/trusted_vault/download_keys_response_handler.h"
 
-#include <algorithm>
+#include <map>
 #include <utility>
 
 #include "components/sync/protocol/vault.pb.h"
@@ -20,52 +20,26 @@
 struct ExtractedSharedKey {
   int version;
   std::vector<uint8_t> trusted_vault_key;
-  std::vector<uint8_t> key_proof;
+  std::vector<uint8_t> rotation_proof;
 };
 
-// Returns pointer to sync security domain in |response|. Returns nullptr if
-// there is no sync security domain.
-const sync_pb::SecurityDomain* FindSyncSecurityDomain(
-    const sync_pb::ListSecurityDomainsResponse& response) {
-  for (const sync_pb::SecurityDomain& security_domain :
-       response.security_domains()) {
-    if (security_domain.name() == kSyncSecurityDomainName) {
-      return &security_domain;
-    }
-  }
-  return nullptr;
-}
-
-// Returns pointer to the member in |response| corresponding to
-// |member_public_key|. Returns nullptr if sync security domain doesn't exist
-// in |response| or there is no such member in sync security domain.
-const sync_pb::SecurityDomain::Member* FindMember(
-    const sync_pb::ListSecurityDomainsResponse& response,
-    const std::vector<uint8_t>& member_public_key_bytes) {
-  const sync_pb::SecurityDomain* sync_security_domain =
-      FindSyncSecurityDomain(response);
-  if (!sync_security_domain) {
-    return nullptr;
-  }
-
-  const std::string member_public_key_string(member_public_key_bytes.begin(),
-                                             member_public_key_bytes.end());
-  for (const sync_pb::SecurityDomain::Member& member :
-       sync_security_domain->members()) {
-    if (member.public_key() == member_public_key_string) {
-      return &member;
+const sync_pb::SecurityDomainMember::SecurityDomainMembership*
+FindSyncMembership(const sync_pb::SecurityDomainMember& member) {
+  for (const auto& membership : member.memberships()) {
+    if (membership.security_domain() == kSyncSecurityDomainName) {
+      return &membership;
     }
   }
   return nullptr;
 }
 
 // Extracts (decrypts |wrapped_key| and converts to ExtractedSharedKey) shared
-// keys from |member| and sorts them by version.
+// keys from |membership| and sorts them by version.
 std::vector<ExtractedSharedKey> ExtractAndSortSharedKeys(
-    const sync_pb::SecurityDomain::Member& member,
+    const sync_pb::SecurityDomainMember::SecurityDomainMembership& membership,
     const SecureBoxPrivateKey& member_private_key) {
-  std::vector<ExtractedSharedKey> result;
-  for (const sync_pb::SharedKey& shared_key : member.keys()) {
+  std::map<int, ExtractedSharedKey> epoch_to_extracted_key;
+  for (const sync_pb::SharedMemberKey& shared_key : membership.keys()) {
     base::Optional<std::vector<uint8_t>> decrypted_key =
         DecryptTrustedVaultWrappedKey(
             member_private_key, ProtoStringToBytes(shared_key.wrapped_key()));
@@ -73,19 +47,29 @@
       // Decryption failed.
       return std::vector<ExtractedSharedKey>();
     }
-    result.push_back(ExtractedSharedKey{
-        /*version=*/shared_key.epoch(), *decrypted_key,
-        /*key_proof=*/ProtoStringToBytes(shared_key.key_proof())});
+    epoch_to_extracted_key[shared_key.epoch()].version = shared_key.epoch();
+    epoch_to_extracted_key[shared_key.epoch()].trusted_vault_key =
+        *decrypted_key;
+  }
+  for (const sync_pb::RotationProof& rotation_proof :
+       membership.rotation_proofs()) {
+    if (epoch_to_extracted_key.count(rotation_proof.new_epoch()) == 0) {
+      // There is no shared key corresponding to rotation proof. In theory it
+      // shouldn't happen, but it's safe to ignore.
+      continue;
+    }
+    epoch_to_extracted_key[rotation_proof.new_epoch()].rotation_proof =
+        ProtoStringToBytes(rotation_proof.rotation_proof());
   }
 
-  std::sort(result.begin(), result.end(),
-            [](const ExtractedSharedKey& a, const ExtractedSharedKey& b) {
-              return a.version < b.version;
-            });
+  std::vector<ExtractedSharedKey> result;
+  for (const auto& epoch_and_extracted_key : epoch_to_extracted_key) {
+    result.push_back(epoch_and_extracted_key.second);
+  }
   return result;
 }
 
-// Validates |key_proof| starting from the key next to
+// Validates |rotation_proof| starting from the key next to
 // last known trusted vault key, returns false if validation fails or |keys|
 // doesn't have a key next to last known trusted vault key.
 bool IsValidKeyChain(
@@ -111,8 +95,8 @@
     }
 
     if (!VerifyTrustedVaultHMAC(last_valid_key, next_key.trusted_vault_key,
-                                next_key.key_proof)) {
-      // |key_proof| isn't valid.
+                                next_key.rotation_proof)) {
+      // |rotation_proof| isn't valid.
       return false;
     }
 
@@ -162,30 +146,32 @@
   switch (http_status) {
     case TrustedVaultRequest::HttpStatus::kSuccess:
       break;
+    case TrustedVaultRequest::HttpStatus::kNotFound:
+    case TrustedVaultRequest::HttpStatus::kFailedPrecondition:
+      // TODO(crbug.com/1113598): expose more detailed status.
+      return ProcessedResponse(
+          /*status=*/TrustedVaultRequestStatus::kLocalDataObsolete);
     case TrustedVaultRequest::HttpStatus::kOtherError:
-    case TrustedVaultRequest::HttpStatus::kBadRequest:
-      // Don't distinguish kBadRequest here, because request content doesn't
-      // depend on the local state.
       return ProcessedResponse(
           /*status=*/TrustedVaultRequestStatus::kOtherError);
   }
 
-  sync_pb::ListSecurityDomainsResponse deserialized_response;
-  if (!deserialized_response.ParseFromString(response_body)) {
+  sync_pb::SecurityDomainMember member;
+  if (!member.ParseFromString(response_body)) {
     return ProcessedResponse(/*status=*/TrustedVaultRequestStatus::kOtherError);
   }
 
-  const sync_pb::SecurityDomain::Member* current_member = FindMember(
-      deserialized_response, device_key_pair_->public_key().ExportToBytes());
-  if (!current_member) {
-    // |device_key_pair_| isn't registered server-side, while client assumes
-    // it's registered when downloading keys.
+  // TODO(crbug.com/1113598): consider validation of member public key.
+  const sync_pb::SecurityDomainMember::SecurityDomainMembership* membership =
+      FindSyncMembership(member);
+  if (!membership) {
+    // Member is not in sync security domain.
     return ProcessedResponse(
         /*status=*/TrustedVaultRequestStatus::kLocalDataObsolete);
   }
 
-  std::vector<ExtractedSharedKey> extracted_keys = ExtractAndSortSharedKeys(
-      *current_member, device_key_pair_->private_key());
+  std::vector<ExtractedSharedKey> extracted_keys =
+      ExtractAndSortSharedKeys(*membership, device_key_pair_->private_key());
   if (extracted_keys.empty()) {
     // |current_member| doesn't have any keys, should be treated as not
     // registered member.
diff --git a/components/sync/trusted_vault/download_keys_response_handler.h b/components/sync/trusted_vault/download_keys_response_handler.h
index 45569ce..7d888786 100644
--- a/components/sync/trusted_vault/download_keys_response_handler.h
+++ b/components/sync/trusted_vault/download_keys_response_handler.h
@@ -17,7 +17,7 @@
 class SecureBoxKeyPair;
 
 // Helper class to extract and validate trusted vault keys from
-// ListSecurityDomainsResponse.
+// GetSecurityDomainMember response.
 class DownloadKeysResponseHandler {
  public:
   struct ProcessedResponse {
@@ -32,10 +32,10 @@
     // kSuccess is reported if extraction was successful and there are new
     // trusted vault keys.
     // kLocalDataObsolete is reported if it's impossible to extract keys due to
-    // data corruption or absence of SecurityDomain/Member or if there is no new
-    // keys.
+    // data corruption or absence of sync SecurityDomainMembership or if there
+    // is no new keys.
     // kOtherError is reported in case of http/network errors or if the response
-    // isn't valid serialized ListSecurityDomainsResponse proto.
+    // isn't valid serialized SecurityDomainMember proto.
     TrustedVaultRequestStatus status;
 
     // Contains new keys (e.g. keys are stored by the server, excluding last
diff --git a/components/sync/trusted_vault/download_keys_response_handler_unittest.cc b/components/sync/trusted_vault/download_keys_response_handler_unittest.cc
index fe81d1c..a49e6bf 100644
--- a/components/sync/trusted_vault/download_keys_response_handler_unittest.cc
+++ b/components/sync/trusted_vault/download_keys_response_handler_unittest.cc
@@ -33,47 +33,47 @@
   return SecureBoxKeyPair::CreateByPrivateKeyImport(private_key_bytes);
 }
 
-// TODO(crbug.com/1113598): consider using TrustedVaultKeyAndVersion instead of
-// |trusted_vault_key| and |trusted_vault_keys_versions|.
-void FillSecurityDomainMember(
-    const SecureBoxPublicKey& public_key,
+void AddSecurityDomainMembership(
+    const std::string& security_domain_name,
+    const SecureBoxPublicKey& member_public_key,
     const std::vector<std::vector<uint8_t>>& trusted_vault_keys,
-    const std::vector<int> trusted_vault_keys_versions,
+    const std::vector<int>& trusted_vault_keys_versions,
     const std::vector<std::vector<uint8_t>>& signing_keys,
-    sync_pb::SecurityDomain::Member* member) {
+    sync_pb::SecurityDomainMember* member) {
   DCHECK(member);
   DCHECK_EQ(trusted_vault_keys.size(), trusted_vault_keys_versions.size());
   DCHECK_EQ(trusted_vault_keys.size(), signing_keys.size());
 
-  AssignBytesToProtoString(public_key.ExportToBytes(),
-                           member->mutable_public_key());
-
+  sync_pb::SecurityDomainMember::SecurityDomainMembership* membership =
+      member->add_memberships();
+  membership->set_security_domain(security_domain_name);
   for (size_t i = 0; i < trusted_vault_keys.size(); ++i) {
-    sync_pb::SharedKey* shared_key = member->add_keys();
+    sync_pb::SharedMemberKey* shared_key = membership->add_keys();
     shared_key->set_epoch(trusted_vault_keys_versions[i]);
     AssignBytesToProtoString(
-        ComputeTrustedVaultWrappedKey(public_key, trusted_vault_keys[i]),
+        ComputeTrustedVaultWrappedKey(member_public_key, trusted_vault_keys[i]),
         shared_key->mutable_wrapped_key());
 
     if (!signing_keys[i].empty()) {
+      sync_pb::RotationProof* rotation_proof =
+          membership->add_rotation_proofs();
+      rotation_proof->set_new_epoch(trusted_vault_keys_versions[i]);
       AssignBytesToProtoString(
           ComputeTrustedVaultHMAC(signing_keys[i], trusted_vault_keys[i]),
-          shared_key->mutable_key_proof());
+          rotation_proof->mutable_rotation_proof());
     }
   }
 }
 
-std::string CreateListSecurityDomainsResponseWithSingleSyncMember(
+std::string CreateGetSecurityDomainMemberResponseWithSyncMembership(
     const std::vector<std::vector<uint8_t>>& trusted_vault_keys,
-    const std::vector<int> trusted_vault_keys_versions,
+    const std::vector<int>& trusted_vault_keys_versions,
     const std::vector<std::vector<uint8_t>>& signing_keys) {
-  sync_pb::ListSecurityDomainsResponse response;
-  sync_pb::SecurityDomain* security_domain = response.add_security_domains();
-  security_domain->set_name(kSyncSecurityDomainName);
-  FillSecurityDomainMember(MakeTestKeyPair()->public_key(), trusted_vault_keys,
-                           trusted_vault_keys_versions, signing_keys,
-                           security_domain->add_members());
-  return response.SerializeAsString();
+  sync_pb::SecurityDomainMember member;
+  AddSecurityDomainMembership(
+      kSyncSecurityDomainName, MakeTestKeyPair()->public_key(),
+      trusted_vault_keys, trusted_vault_keys_versions, signing_keys, &member);
+  return member.SerializeAsString();
 }
 
 class DownloadKeysResponseHandlerTest : public testing::Test {
@@ -97,17 +97,23 @@
   const DownloadKeysResponseHandler handler_;
 };
 
-// All HttpStatuses except kSuccess should end up in kOtherError reporting,
-// because underlying request doesn't have any parameters inferred from local
-// state.
+// All HttpStatuses except kSuccess should end up in kOtherError or
+// kLocalDataObsolete reporting.
 TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleHttpErrors) {
   EXPECT_THAT(
       handler()
           .ProcessResponse(
-              /*http_status=*/TrustedVaultRequest::HttpStatus::kBadRequest,
+              /*http_status=*/TrustedVaultRequest::HttpStatus::kNotFound,
               /*response_body=*/std::string())
           .status,
-      Eq(TrustedVaultRequestStatus::kOtherError));
+      Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
+  EXPECT_THAT(handler()
+                  .ProcessResponse(
+                      /*http_status=*/TrustedVaultRequest::HttpStatus::
+                          kFailedPrecondition,
+                      /*response_body=*/std::string())
+                  .status,
+              Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
   EXPECT_THAT(
       handler()
           .ProcessResponse(
@@ -124,7 +130,7 @@
       handler().ProcessResponse(
           /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
           /*response_body=*/
-          CreateListSecurityDomainsResponseWithSingleSyncMember(
+          CreateGetSecurityDomainMemberResponseWithSyncMembership(
               /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
               /*trusted_vault_keys_versions=*/
               {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
@@ -144,7 +150,7 @@
       handler().ProcessResponse(
           /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
           /*response_body=*/
-          CreateListSecurityDomainsResponseWithSingleSyncMember(
+          CreateGetSecurityDomainMemberResponseWithSyncMembership(
               /*trusted_vault_keys=*/
               {kKnownTrustedVaultKey, kTrustedVaultKey1, kTrustedVaultKey2},
               /*trusted_vault_keys_versions=*/
@@ -170,7 +176,7 @@
       handler().ProcessResponse(
           /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
           /*response_body=*/
-          CreateListSecurityDomainsResponseWithSingleSyncMember(
+          CreateGetSecurityDomainMemberResponseWithSyncMembership(
               /*trusted_vault_keys=*/
               {kTrustedVaultKey1, kKnownTrustedVaultKey, kTrustedVaultKey2,
                kTrustedVaultKey3},
@@ -203,7 +209,7 @@
       handler().ProcessResponse(
           /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
           /*response_body=*/
-          CreateListSecurityDomainsResponseWithSingleSyncMember(
+          CreateGetSecurityDomainMemberResponseWithSyncMembership(
               /*trusted_vault_keys=*/
               {kTrustedVaultKey1, kTrustedVaultKey2},
               /*trusted_vault_keys_versions=*/
@@ -232,7 +238,7 @@
       handler().ProcessResponse(
           /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
           /*response_body=*/
-          CreateListSecurityDomainsResponseWithSingleSyncMember(
+          CreateGetSecurityDomainMemberResponseWithSyncMembership(
               /*trusted_vault_keys=*/
               {kTrustedVaultKey2, kTrustedVaultKey3},
               /*trusted_vault_keys_versions=*/
@@ -250,38 +256,36 @@
 // should return kLocalDataObsolete to allow client to restore Member by
 // re-registration.
 TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleUndecryptableKey) {
-  sync_pb::ListSecurityDomainsResponse response;
-  sync_pb::SecurityDomain* security_domain = response.add_security_domains();
-  security_domain->set_name(kSyncSecurityDomainName);
-  sync_pb::SecurityDomain::Member* member = security_domain->add_members();
-  FillSecurityDomainMember(
-      MakeTestKeyPair()->public_key(),
+  sync_pb::SecurityDomainMember member;
+  AddSecurityDomainMembership(
+      kSyncSecurityDomainName, MakeTestKeyPair()->public_key(),
       /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
       /*trusted_vault_keys_versions=*/
       {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
-      /*signing_keys=*/{{}, kKnownTrustedVaultKey}, member);
+      /*signing_keys=*/{{}, kKnownTrustedVaultKey}, &member);
 
   // Corrupt wrapped key corresponding to kTrustedVaultKey1.
-  member->mutable_keys(1)->set_wrapped_key("undecryptable_key");
+  member.mutable_memberships(0)->mutable_keys(1)->set_wrapped_key(
+      "undecryptable_key");
 
   EXPECT_THAT(handler()
                   .ProcessResponse(
                       /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
-                      /*response_body=*/response.SerializeAsString())
+                      /*response_body=*/member.SerializeAsString())
                   .status,
               Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
 }
 
-// The test populates invalid |key_proof| field for the single key rotation.
-// kTrustedVaultKey1 is expected to be signed with kKnownTrustedVaultKey, but
-// instead it's signed with kTrustedVaultKey2.
+// The test populates invalid |rotation_proof| field for the single key
+// rotation. kTrustedVaultKey1 is expected to be signed with
+// kKnownTrustedVaultKey, but instead it's signed with kTrustedVaultKey2.
 TEST_F(DownloadKeysResponseHandlerTest,
        ShouldHandleInvalidKeyProofOnSingleKeyRotation) {
   const DownloadKeysResponseHandler::ProcessedResponse processed_response =
       handler().ProcessResponse(
           /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
           /*response_body=*/
-          CreateListSecurityDomainsResponseWithSingleSyncMember(
+          CreateGetSecurityDomainMemberResponseWithSyncMembership(
               /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
               /*trusted_vault_keys_versions=*/
               {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
@@ -292,7 +296,7 @@
   EXPECT_THAT(processed_response.new_keys, IsEmpty());
 }
 
-// The test populates invalid |key_proof| field for intermediate key when
+// The test populates invalid |rotation_proof| field for intermediate key when
 // multiple key rotations have happened.
 // kTrustedVaultKey1 is expected to be signed with kKnownTrustedVaultKey, but
 // instead it's signed with kTrustedVaultKey2.
@@ -302,7 +306,7 @@
       handler().ProcessResponse(
           /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
           /*response_body=*/
-          CreateListSecurityDomainsResponseWithSingleSyncMember(
+          CreateGetSecurityDomainMemberResponseWithSyncMembership(
               /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1,
                                       kTrustedVaultKey2},
               /*trusted_vault_keys_versions=*/
@@ -324,7 +328,7 @@
                   .ProcessResponse(
                       /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
                       /*response_body=*/
-                      CreateListSecurityDomainsResponseWithSingleSyncMember(
+                      CreateGetSecurityDomainMemberResponseWithSyncMembership(
                           /*trusted_vault_keys=*/{kKnownTrustedVaultKey},
                           /*trusted_vault_keys_versions=*/
                           {kKnownTrustedVaultKeyVersion},
@@ -334,7 +338,7 @@
 }
 
 // Tests handling the situation, when response isn't a valid serialized
-// ListSecurityDomains proto.
+// SecurityDomainMemberProto proto.
 TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleCorruptedResponseProto) {
   EXPECT_THAT(handler()
                   .ProcessResponse(
@@ -344,50 +348,56 @@
               Eq(TrustedVaultRequestStatus::kOtherError));
 }
 
-// Client expects that the security domain exists, but the response indicates
-// it doesn't by having no security domains.
-TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleEmptyResponse) {
+// Client expects that the sync security domain membership exists, but the
+// response indicates it doesn't by having no memberships.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleAbsenseOfMemberships) {
   EXPECT_THAT(handler()
                   .ProcessResponse(
                       /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
-                      /*response_body=*/std::string())
+                      /*response_body=*/sync_pb::SecurityDomainMember()
+                          .SerializeAsString())
                   .status,
               Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
 }
 
-// Same as above, but there is a different security domain.
-TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleAbsenseOfSecurityDomain) {
-  sync_pb::ListSecurityDomainsResponse response;
-  sync_pb::SecurityDomain* security_domain = response.add_security_domains();
-  security_domain->set_name("other_domain");
+// Same as above, but there is a different security domain membership.
+TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleAbsenseOfSyncMembership) {
+  sync_pb::SecurityDomainMember member;
+  AddSecurityDomainMembership(
+      "other_domain", MakeTestKeyPair()->public_key(),
+      /*trusted_vault_keys=*/{kTrustedVaultKey1},
+      /*trusted_vault_keys_versions=*/{kKnownTrustedVaultKeyVersion + 1},
+      /*signing_keys=*/{kKnownTrustedVaultKey}, &member);
 
   EXPECT_THAT(handler()
                   .ProcessResponse(
                       /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
-                      /*response_body=*/response.SerializeAsString())
+                      /*response_body=*/member.SerializeAsString())
                   .status,
               Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
 }
 
-// Tests handling presence of other security domains.
+// Tests handling presence of other security domain memberships.
 TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleMultipleSecurityDomains) {
-  sync_pb::ListSecurityDomainsResponse response;
-  sync_pb::SecurityDomain* other_domain = response.add_security_domains();
-  other_domain->set_name("other_domain");
+  sync_pb::SecurityDomainMember member;
+  AddSecurityDomainMembership(
+      "other_domain", MakeTestKeyPair()->public_key(),
+      /*trusted_vault_keys=*/{kTrustedVaultKey1},
+      /*trusted_vault_keys_versions=*/{kKnownTrustedVaultKeyVersion + 1},
+      /*signing_keys=*/{{}}, &member);
 
-  sync_pb::SecurityDomain* sync_domain = response.add_security_domains();
-  sync_domain->set_name(kSyncSecurityDomainName);
-  FillSecurityDomainMember(
-      /*public_key=*/MakeTestKeyPair()->public_key(),
-      /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
-      /*trusted_vault_keys_versions=*/
-      {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
-      /*signing_keys=*/{{}, kKnownTrustedVaultKey}, sync_domain->add_members());
+  // Note: sync security domain membership is different by having correct
+  // rotation proof.
+  AddSecurityDomainMembership(
+      kSyncSecurityDomainName, MakeTestKeyPair()->public_key(),
+      /*trusted_vault_keys=*/{kTrustedVaultKey1},
+      /*trusted_vault_keys_versions=*/{kKnownTrustedVaultKeyVersion + 1},
+      /*signing_keys=*/{kKnownTrustedVaultKey}, &member);
 
   const DownloadKeysResponseHandler::ProcessedResponse processed_response =
       handler().ProcessResponse(
           /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
-          /*response_body=*/response.SerializeAsString());
+          /*response_body=*/member.SerializeAsString());
 
   EXPECT_THAT(processed_response.status,
               Eq(TrustedVaultRequestStatus::kSuccess));
@@ -396,80 +406,6 @@
               Eq(kKnownTrustedVaultKeyVersion + 1));
 }
 
-// Security domain exists, but doesn't contain member corresponding to the
-// current device.
-TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleAbsenseOfMember) {
-  sync_pb::ListSecurityDomainsResponse response;
-  sync_pb::SecurityDomain* security_domain = response.add_security_domains();
-  security_domain->set_name(kSyncSecurityDomainName);
-
-  FillSecurityDomainMember(
-      /*public_key=*/SecureBoxKeyPair::GenerateRandom()->public_key(),
-      /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
-      /*trusted_vault_keys_versions=*/
-      {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
-      /*signing_keys=*/{{}, kKnownTrustedVaultKey},
-      security_domain->add_members());
-
-  EXPECT_THAT(handler()
-                  .ProcessResponse(
-                      /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
-                      /*response_body=*/response.SerializeAsString())
-                  .status,
-              Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
-}
-
-// Tests handling presence of other members.
-TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleMultipleMembers) {
-  sync_pb::ListSecurityDomainsResponse response;
-  sync_pb::SecurityDomain* security_domain = response.add_security_domains();
-  security_domain->set_name(kSyncSecurityDomainName);
-
-  // Other member.
-  FillSecurityDomainMember(
-      /*public_key=*/SecureBoxKeyPair::GenerateRandom()->public_key(),
-      /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
-      /*trusted_vault_keys_versions=*/
-      {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
-      /*signing_keys=*/{{}, kKnownTrustedVaultKey},
-      security_domain->add_members());
-
-  // Member corresponding to the current device.
-  FillSecurityDomainMember(
-      /*public_key=*/MakeTestKeyPair()->public_key(),
-      /*trusted_vault_keys=*/{kKnownTrustedVaultKey, kTrustedVaultKey1},
-      /*trusted_vault_keys_versions=*/
-      {kKnownTrustedVaultKeyVersion, kKnownTrustedVaultKeyVersion + 1},
-      /*signing_keys=*/{{}, kKnownTrustedVaultKey},
-      security_domain->add_members());
-
-  const DownloadKeysResponseHandler::ProcessedResponse processed_response =
-      handler().ProcessResponse(
-          /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
-          /*response_body=*/response.SerializeAsString());
-
-  EXPECT_THAT(processed_response.status,
-              Eq(TrustedVaultRequestStatus::kSuccess));
-  EXPECT_THAT(processed_response.new_keys, ElementsAre(kTrustedVaultKey1));
-  EXPECT_THAT(processed_response.last_key_version,
-              Eq(kKnownTrustedVaultKeyVersion + 1));
-}
-
-// Corrupted data case: the member corresponding to the current device exists,
-// but has no keys.
-TEST_F(DownloadKeysResponseHandlerTest, ShouldHandleEmptyMember) {
-  EXPECT_THAT(handler()
-                  .ProcessResponse(
-                      /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
-                      /*response_body=*/
-                      CreateListSecurityDomainsResponseWithSingleSyncMember(
-                          /*trusted_vault_keys=*/{},
-                          /*trusted_vault_keys_versions=*/{},
-                          /*signing_keys=*/{}))
-                  .status,
-              Eq(TrustedVaultRequestStatus::kLocalDataObsolete));
-}
-
 // Test scenario, when no trusted vault keys available on the current device
 // (e.g. device was registered with constant key). In this case new keys
 // shouldn't be validated.
@@ -483,7 +419,7 @@
       handler.ProcessResponse(
           /*http_status=*/TrustedVaultRequest::HttpStatus::kSuccess,
           /*response_body=*/
-          CreateListSecurityDomainsResponseWithSingleSyncMember(
+          CreateGetSecurityDomainMemberResponseWithSyncMembership(
               /*trusted_vault_keys=*/{kTrustedVaultKey1},
               /*trusted_vault_keys_versions=*/{kLastKeyVersion},
               /*signing_keys=*/{{}}));
diff --git a/components/sync/trusted_vault/fake_security_domains_server.cc b/components/sync/trusted_vault/fake_security_domains_server.cc
new file mode 100644
index 0000000..d8de149
--- /dev/null
+++ b/components/sync/trusted_vault/fake_security_domains_server.cc
@@ -0,0 +1,198 @@
+// 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/sync/trusted_vault/fake_security_domains_server.h"
+
+#include "base/base64url.h"
+#include "components/sync/trusted_vault/proto_string_bytes_conversion.h"
+#include "components/sync/trusted_vault/trusted_vault_crypto.h"
+#include "components/sync/trusted_vault/trusted_vault_server_constants.h"
+
+namespace syncer {
+
+namespace {
+
+const char kServerPathPrefix[] = "sds";
+
+std::unique_ptr<net::test_server::HttpResponse>
+CreateHttpResponseForInvalidRequest() {
+  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+  // TODO(crbug.com/1113599): HTTP_BAD_REQUEST probably fits better, but
+  // currently used for the other purpose. Replace when possible.
+  response->set_code(net::HTTP_INTERNAL_SERVER_ERROR);
+  return response;
+}
+
+// Returns whether |request| satisfies protocol expectations.
+bool ValidateJoinSecurityDomainsRequest(
+    const sync_pb::JoinSecurityDomainsRequest& request) {
+  if (request.security_domain().name() != kSyncSecurityDomainName) {
+    DVLOG(1)
+        << "JoinSecurityDomains request has unexpected security domain name: "
+        << request.security_domain().name();
+    return false;
+  }
+
+  const sync_pb::SecurityDomainMember& member =
+      request.security_domain_member();
+  if (member.public_key().empty()) {
+    DVLOG(1) << "JoinSecurityDomains request has empty member public key";
+    return false;
+  }
+
+  std::string encoded_public_key;
+  base::Base64UrlEncode(member.public_key(),
+                        base::Base64UrlEncodePolicy::OMIT_PADDING,
+                        &encoded_public_key);
+  if (member.name() != kSecurityDomainMemberNamePrefix + encoded_public_key) {
+    DVLOG(1) << "JoinSecurityDomains request has inconsistent member name ("
+             << member.name() << ") and public key (" << member.public_key()
+             << ")";
+    return false;
+  }
+
+  const sync_pb::SharedMemberKey& shared_key = request.shared_member_key();
+  if (shared_key.wrapped_key().empty()) {
+    DVLOG(1)
+        << "JoinSecurityDomains request has shared key with empty wrapped key";
+    return false;
+  }
+
+  if (shared_key.member_proof().empty()) {
+    DVLOG(1)
+        << "JoinSecurityDomains request has shared key with empty wrapped key";
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+FakeSecurityDomainsServer::FakeSecurityDomainsServer(GURL base_url)
+    : server_url_(base_url.spec() + kServerPathPrefix) {}
+
+FakeSecurityDomainsServer::~FakeSecurityDomainsServer() = default;
+
+std::unique_ptr<net::test_server::HttpResponse>
+FakeSecurityDomainsServer::HandleRequest(
+    const net::test_server::HttpRequest& http_request) {
+  DVLOG(1) << "Received request";
+  if (base::StartsWith(http_request.relative_url, kServerPathPrefix)) {
+    // This request shouldn't be handled by security domains server.
+    return nullptr;
+  }
+
+  if (http_request.GetURL() ==
+      GetFullJoinSecurityDomainsURLForTesting(server_url_)) {
+    return HandleJoinSecurityDomainsRequest(http_request);
+  }
+
+  DVLOG(1) << "Unknown request url: " << http_request.GetURL().spec();
+  received_invalid_request_ = true;
+
+  return CreateHttpResponseForInvalidRequest();
+}
+
+int FakeSecurityDomainsServer::GetMemberCount() const {
+  return public_key_to_shared_keys_.size();
+}
+
+bool FakeSecurityDomainsServer::AllMembersHaveKey(
+    const std::vector<uint8_t>& trusted_vault_key) const {
+  for (const auto& public_key_and_shared_keys : public_key_to_shared_keys_) {
+    bool member_has_key = false;
+    for (const auto& shared_key : public_key_and_shared_keys.second) {
+      // Member has |trusted_vault_key| if there is a member proof signed by
+      // |trusted_vault_key|.
+      if (VerifyTrustedVaultHMAC(
+              /*key=*/trusted_vault_key,
+              /*data=*/ProtoStringToBytes(public_key_and_shared_keys.first),
+              /*digest=*/ProtoStringToBytes(shared_key.member_proof()))) {
+        member_has_key = true;
+        break;
+      }
+    }
+    if (!member_has_key) {
+      return false;
+    }
+  }
+  return true;
+}
+
+std::unique_ptr<net::test_server::HttpResponse>
+FakeSecurityDomainsServer::HandleJoinSecurityDomainsRequest(
+    const net::test_server::HttpRequest& http_request) {
+  if (http_request.method != net::test_server::METHOD_POST) {
+    DVLOG(1) << "JoinSecurityDomains request has wrong method: "
+             << http_request.method;
+    received_invalid_request_ = true;
+    return CreateHttpResponseForInvalidRequest();
+  }
+  // TODO(crbug.com/1113599): consider verifying content type and access token
+  // headers.
+
+  sync_pb::JoinSecurityDomainsRequest deserialized_content;
+  if (!deserialized_content.ParseFromString(http_request.content)) {
+    DVLOG(1) << "Failed to deserialize JoinSecurityDomains request content";
+    received_invalid_request_ = true;
+    return CreateHttpResponseForInvalidRequest();
+  }
+
+  if (!ValidateJoinSecurityDomainsRequest(deserialized_content)) {
+    received_invalid_request_ = true;
+    return CreateHttpResponseForInvalidRequest();
+  }
+
+  const sync_pb::SecurityDomainMember& member =
+      deserialized_content.security_domain_member();
+  if (public_key_to_shared_keys_.count(member.public_key()) != 0) {
+    // Member already exists.
+    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+    response->set_code(net::HTTP_PRECONDITION_FAILED);
+    return response;
+  }
+
+  const sync_pb::SharedMemberKey& shared_key =
+      deserialized_content.shared_member_key();
+  if (shared_key.has_epoch() && shared_key.epoch() != current_epoch_) {
+    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+    response->set_code(net::HTTP_PRECONDITION_FAILED);
+    return response;
+  }
+
+  if (shared_key.has_epoch()) {
+    // Valid joining of existing security domain.
+    public_key_to_shared_keys_[member.public_key()] = {shared_key};
+    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+    response->set_code(net::HTTP_OK);
+    return response;
+  }
+
+  if (!constant_key_allowed_ ||
+      !VerifyTrustedVaultHMAC(
+          /*key=*/GetConstantTrustedVaultKey(),
+          /*data=*/ProtoStringToBytes(member.public_key()),
+          /*digest=*/ProtoStringToBytes(shared_key.member_proof()))) {
+    // Either constant key is not allowed, or request uses the real key without
+    // populating the epoch.
+    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+    response->set_code(net::HTTP_PRECONDITION_FAILED);
+    return response;
+  }
+
+  // Valid joining with constant key.
+  if (current_epoch_ == 0) {
+    // Simulate generation of random epoch when security domain just created.
+    DCHECK(public_key_to_shared_keys_.empty());
+    current_epoch_ = 100;
+  }
+
+  public_key_to_shared_keys_[member.public_key()] = {shared_key};
+  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+  response->set_code(net::HTTP_OK);
+  return response;
+}
+
+}  // namespace syncer
diff --git a/components/sync/trusted_vault/fake_security_domains_server.h b/components/sync/trusted_vault/fake_security_domains_server.h
new file mode 100644
index 0000000..51aba41
--- /dev/null
+++ b/components/sync/trusted_vault/fake_security_domains_server.h
@@ -0,0 +1,63 @@
+// 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 COMPONENTS_SYNC_TRUSTED_VAULT_FAKE_SECURITY_DOMAINS_SERVER_H_
+#define COMPONENTS_SYNC_TRUSTED_VAULT_FAKE_SECURITY_DOMAINS_SERVER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "components/sync/protocol/vault.pb.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "url/gurl.h"
+
+namespace syncer {
+
+// Mimics behavior of the security domains server. This class is designed to be
+// used with EmbeddedTestServer via registration of HandleRequest() method.
+class FakeSecurityDomainsServer {
+ public:
+  explicit FakeSecurityDomainsServer(GURL base_url);
+  FakeSecurityDomainsServer(const FakeSecurityDomainsServer& other) = delete;
+  FakeSecurityDomainsServer& operator=(const FakeSecurityDomainsServer& other) =
+      delete;
+  ~FakeSecurityDomainsServer();
+
+  // Handles request if it belongs to security domains server (identified by
+  // request url). Returns nullptr otherwise.
+  std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+      const net::test_server::HttpRequest& http_request);
+
+  int GetMemberCount() const;
+  bool AllMembersHaveKey(const std::vector<uint8_t>& trusted_vault_key) const;
+
+  GURL server_url() const { return server_url_; }
+  // Returns true if there was a request that violates supported protocol.
+  bool received_invalid_request() const { return received_invalid_request_; }
+
+ private:
+  std::unique_ptr<net::test_server::HttpResponse>
+  HandleJoinSecurityDomainsRequest(
+      const net::test_server::HttpRequest& http_request);
+
+  bool received_invalid_request_ = false;
+  const GURL server_url_;
+
+  // Maps members public key to shared keys that belong to this member.
+  std::map<std::string, std::vector<sync_pb::SharedMemberKey>>
+      public_key_to_shared_keys_;
+  // Zero epoch is used when there are no members in the security domain, once
+  // first member is joined it is initialized to non-zero value. Members still
+  // can join with constant key without populating epoch while
+  // |constant_key_allowed_| set to true.
+  int current_epoch_ = 0;
+  bool constant_key_allowed_ = true;
+};
+
+}  // namespace syncer
+
+#endif  // COMPONENTS_SYNC_TRUSTED_VAULT_FAKE_SECURITY_DOMAINS_SERVER_H_
diff --git a/components/sync/trusted_vault/standalone_trusted_vault_backend.cc b/components/sync/trusted_vault/standalone_trusted_vault_backend.cc
index 05606b5..340c931 100644
--- a/components/sync/trusted_vault/standalone_trusted_vault_backend.cc
+++ b/components/sync/trusted_vault/standalone_trusted_vault_backend.cc
@@ -322,7 +322,7 @@
   // safe to use base::Unretained() here.
   ongoing_connection_request_ = connection_->RegisterAuthenticationFactor(
       *primary_account_, last_trusted_vault_key_and_version,
-      key_pair->public_key(),
+      key_pair->public_key(), AuthenticationFactorType::kPhysicalDevice,
       base::BindOnce(&StandaloneTrustedVaultBackend::OnDeviceRegistered,
                      base::Unretained(this), gaia_id));
   DCHECK(ongoing_connection_request_);
diff --git a/components/sync/trusted_vault/standalone_trusted_vault_backend_unittest.cc b/components/sync/trusted_vault/standalone_trusted_vault_backend_unittest.cc
index bc5de71..796719e5 100644
--- a/components/sync/trusted_vault/standalone_trusted_vault_backend_unittest.cc
+++ b/components/sync/trusted_vault/standalone_trusted_vault_backend_unittest.cc
@@ -73,6 +73,7 @@
                const base::Optional<TrustedVaultKeyAndVersion>&
                    last_trusted_vault_key_and_version,
                const SecureBoxPublicKey& authentication_factor_public_key,
+               AuthenticationFactorType authentication_factor_type,
                RegisterAuthenticationFactorCallback callback),
               (override));
   MOCK_METHOD(std::unique_ptr<Request>,
@@ -137,11 +138,12 @@
                     Eq(account_info),
                     OptionalTrustedVaultKeyAndVersionEq(vault_keys.back(),
                                                         last_vault_key_version),
-                    _, _))
+                    _, AuthenticationFactorType::kPhysicalDevice, _))
         .WillOnce(
             [&](const CoreAccountInfo&,
                 const base::Optional<TrustedVaultKeyAndVersion>&,
                 const SecureBoxPublicKey& device_public_key,
+                AuthenticationFactorType,
                 TrustedVaultConnection::RegisterAuthenticationFactorCallback
                     callback) {
               device_registration_callback = std::move(callback);
@@ -324,14 +326,16 @@
   TrustedVaultConnection::RegisterAuthenticationFactorCallback
       device_registration_callback;
   std::vector<uint8_t> serialized_public_device_key;
-  EXPECT_CALL(*connection(),
-              RegisterAuthenticationFactor(Eq(account_info),
-                                           OptionalTrustedVaultKeyAndVersionEq(
-                                               kVaultKey, kLastKeyVersion),
-                                           _, _))
+  EXPECT_CALL(
+      *connection(),
+      RegisterAuthenticationFactor(
+          Eq(account_info),
+          OptionalTrustedVaultKeyAndVersionEq(kVaultKey, kLastKeyVersion), _,
+          AuthenticationFactorType::kPhysicalDevice, _))
       .WillOnce([&](const CoreAccountInfo&,
                     const base::Optional<TrustedVaultKeyAndVersion>&,
                     const SecureBoxPublicKey& device_public_key,
+                    AuthenticationFactorType,
                     TrustedVaultConnection::RegisterAuthenticationFactorCallback
                         callback) {
         serialized_public_device_key = device_public_key.ExportToBytes();
@@ -371,11 +375,11 @@
   backend()->StoreKeys(account_info.gaia, {kVaultKey}, kLastKeyVersion);
   TrustedVaultConnection::RegisterAuthenticationFactorCallback
       device_registration_callback;
-  ON_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _))
+  ON_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _))
       .WillByDefault(
           [&](const CoreAccountInfo&,
               const base::Optional<TrustedVaultKeyAndVersion>&,
-              const SecureBoxPublicKey&,
+              const SecureBoxPublicKey&, AuthenticationFactorType,
               TrustedVaultConnection::RegisterAuthenticationFactorCallback
                   callback) {
             device_registration_callback = std::move(callback);
@@ -384,7 +388,7 @@
 
   clock()->SetNow(base::Time::Now());
 
-  EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _));
+  EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _));
   // Setting the primary account will trigger device registration.
   backend()->SetPrimaryAccount(account_info);
   ASSERT_FALSE(device_registration_callback.is_null());
@@ -397,7 +401,8 @@
   // Following request should be throttled.
   device_registration_callback =
       TrustedVaultConnection::RegisterAuthenticationFactorCallback();
-  EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _)).Times(0);
+  EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _))
+      .Times(0);
   // Reset and set primary account to trigger device registration attempt.
   backend()->SetPrimaryAccount(base::nullopt);
   backend()->SetPrimaryAccount(account_info);
@@ -407,7 +412,7 @@
   // Advance time to pass the throttling duration and trigger another attempt.
   clock()->Advance(switches::kTrustedVaultServiceThrottlingDuration.Get());
 
-  EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _));
+  EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _));
   // Reset and set primary account to trigger device registration attempt.
   backend()->SetPrimaryAccount(base::nullopt);
   backend()->SetPrimaryAccount(account_info);
@@ -427,11 +432,11 @@
   backend()->StoreKeys(account_info.gaia, {kVaultKey}, kLastKeyVersion);
   TrustedVaultConnection::RegisterAuthenticationFactorCallback
       device_registration_callback;
-  ON_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _))
+  ON_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _))
       .WillByDefault(
           [&](const CoreAccountInfo&,
               const base::Optional<TrustedVaultKeyAndVersion>&,
-              const SecureBoxPublicKey&,
+              const SecureBoxPublicKey&, AuthenticationFactorType,
               TrustedVaultConnection::RegisterAuthenticationFactorCallback
                   callback) {
             device_registration_callback = std::move(callback);
@@ -440,7 +445,7 @@
 
   clock()->SetNow(base::Time::Now());
 
-  EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _));
+  EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _));
   // Setting the primary account will trigger device registration.
   backend()->SetPrimaryAccount(account_info);
   ASSERT_FALSE(device_registration_callback.is_null());
@@ -455,7 +460,7 @@
 
   device_registration_callback =
       TrustedVaultConnection::RegisterAuthenticationFactorCallback();
-  EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _));
+  EXPECT_CALL(*connection(), RegisterAuthenticationFactor(_, _, _, _, _));
   // Reset and set primary account to trigger device registration attempt.
   backend()->SetPrimaryAccount(base::nullopt);
   backend()->SetPrimaryAccount(account_info);
@@ -605,10 +610,11 @@
       *connection(),
       RegisterAuthenticationFactor(
           account_info,
-          /*last_trusted_vault_key_and_version=*/Eq(base::nullopt), _, _))
+          /*last_trusted_vault_key_and_version=*/Eq(base::nullopt), _, _, _))
       .WillOnce([&](const CoreAccountInfo&,
                     const base::Optional<TrustedVaultKeyAndVersion>&,
                     const SecureBoxPublicKey& device_public_key,
+                    AuthenticationFactorType,
                     TrustedVaultConnection::RegisterAuthenticationFactorCallback
                         callback) {
         device_registration_callback = std::move(callback);
diff --git a/components/sync/trusted_vault/trusted_vault_connection.h b/components/sync/trusted_vault/trusted_vault_connection.h
index 561879d..46a7a89 100644
--- a/components/sync/trusted_vault/trusted_vault_connection.h
+++ b/components/sync/trusted_vault/trusted_vault_connection.h
@@ -28,6 +28,8 @@
   kOtherError
 };
 
+enum class AuthenticationFactorType { kPhysicalDevice };
+
 struct TrustedVaultKeyAndVersion {
   TrustedVaultKeyAndVersion(const std::vector<uint8_t>& key, int version);
   TrustedVaultKeyAndVersion(const TrustedVaultKeyAndVersion& other);
@@ -77,6 +79,7 @@
       const base::Optional<TrustedVaultKeyAndVersion>&
           last_trusted_vault_key_and_version,
       const SecureBoxPublicKey& authentication_factor_public_key,
+      AuthenticationFactorType authentication_factor_type,
       RegisterAuthenticationFactorCallback callback) WARN_UNUSED_RESULT = 0;
 
   // Asynchronously attempts to download new vault keys (e.g. keys with version
diff --git a/components/sync/trusted_vault/trusted_vault_connection_impl.cc b/components/sync/trusted_vault/trusted_vault_connection_impl.cc
index a0e0b19..9214e19 100644
--- a/components/sync/trusted_vault/trusted_vault_connection_impl.cc
+++ b/components/sync/trusted_vault/trusted_vault_connection_impl.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/base64url.h"
 #include "base/containers/span.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/sync/protocol/vault.pb.h"
@@ -22,49 +23,67 @@
 
 namespace {
 
-sync_pb::SharedKey CreateMemberSharedKey(
+sync_pb::SharedMemberKey CreateSharedMemberKey(
     const base::Optional<TrustedVaultKeyAndVersion>&
         trusted_vault_key_and_version,
     const SecureBoxPublicKey& public_key) {
   std::vector<uint8_t> trusted_vault_key;
-  int trusted_vault_key_version = 0;
   if (trusted_vault_key_and_version.has_value()) {
     trusted_vault_key = trusted_vault_key_and_version->key;
-    trusted_vault_key_version = trusted_vault_key_and_version->version;
   } else {
     trusted_vault_key = GetConstantTrustedVaultKey();
   }
 
-  sync_pb::SharedKey shared_key;
-  shared_key.set_epoch(trusted_vault_key_version);
+  sync_pb::SharedMemberKey shared_member_key;
+  if (trusted_vault_key_and_version.has_value()) {
+    shared_member_key.set_epoch(trusted_vault_key_and_version->version);
+  }
   AssignBytesToProtoString(
       ComputeTrustedVaultWrappedKey(public_key, trusted_vault_key),
-      shared_key.mutable_wrapped_key());
+      shared_member_key.mutable_wrapped_key());
   AssignBytesToProtoString(
-      ComputeTrustedVaultHMAC(/*key=*/trusted_vault_key,
-                              /*data=*/public_key.ExportToBytes()),
-      shared_key.mutable_member_proof());
-  return shared_key;
+      ComputeTrustedVaultHMAC(
+          /*key=*/trusted_vault_key, /*data=*/public_key.ExportToBytes()),
+      shared_member_key.mutable_member_proof());
+  return shared_member_key;
+}
+
+sync_pb::SecurityDomainMember CreateSecurityDomainMember(
+    const SecureBoxPublicKey& public_key,
+    AuthenticationFactorType authentication_factor_type) {
+  sync_pb::SecurityDomainMember member;
+  std::string public_key_string;
+  AssignBytesToProtoString(public_key.ExportToBytes(), &public_key_string);
+
+  std::string encoded_public_key;
+  base::Base64UrlEncode(public_key_string,
+                        base::Base64UrlEncodePolicy::OMIT_PADDING,
+                        &encoded_public_key);
+
+  member.set_name(kSecurityDomainMemberNamePrefix + encoded_public_key);
+  // Note: |public_key_string| using here is intentional, encoding is required
+  // only to compute member name.
+  member.set_public_key(public_key_string);
+  switch (authentication_factor_type) {
+    case AuthenticationFactorType::kPhysicalDevice:
+      member.set_member_type(
+          sync_pb::SecurityDomainMember::MEMBER_TYPE_PHYSICAL_DEVICE);
+  }
+  return member;
 }
 
 sync_pb::JoinSecurityDomainsRequest CreateJoinSecurityDomainsRequest(
     const base::Optional<TrustedVaultKeyAndVersion>&
         last_trusted_vault_key_and_version,
-    const SecureBoxPublicKey& public_key) {
-  sync_pb::SecurityDomain::Member member;
-  const std::vector<uint8_t> public_key_bytes = public_key.ExportToBytes();
-  AssignBytesToProtoString(public_key.ExportToBytes(),
-                           member.mutable_public_key());
-  *member.add_keys() =
-      CreateMemberSharedKey(last_trusted_vault_key_and_version, public_key);
-
-  sync_pb::SecurityDomain security_domain;
-  security_domain.set_name(kSyncSecurityDomainName);
-  *security_domain.add_members() = member;
-
-  sync_pb::JoinSecurityDomainsRequest result;
-  *result.add_security_domains() = security_domain;
-  return result;
+    const SecureBoxPublicKey& public_key,
+    AuthenticationFactorType authentication_factor_type) {
+  sync_pb::JoinSecurityDomainsRequest request;
+  request.mutable_security_domain()->set_name(kSyncSecurityDomainName);
+  *request.mutable_security_domain_member() =
+      CreateSecurityDomainMember(public_key, authentication_factor_type);
+  *request.mutable_shared_member_key() =
+      CreateSharedMemberKey(last_trusted_vault_key_and_version, public_key);
+  return request;
 }
 
 void ProcessRegisterAuthenticationFactorRequest(
@@ -78,7 +97,9 @@
     case TrustedVaultRequest::HttpStatus::kOtherError:
       std::move(callback).Run(TrustedVaultRequestStatus::kOtherError);
       return;
-    case TrustedVaultRequest::HttpStatus::kBadRequest:
+    case TrustedVaultRequest::HttpStatus::kNotFound:
+    case TrustedVaultRequest::HttpStatus::kFailedPrecondition:
+      // Local trusted vault keys are outdated.
       std::move(callback).Run(TrustedVaultRequestStatus::kLocalDataObsolete);
       return;
   }
@@ -118,13 +139,14 @@
     const base::Optional<TrustedVaultKeyAndVersion>&
         last_trusted_vault_key_and_version,
     const SecureBoxPublicKey& public_key,
+    AuthenticationFactorType authentication_factor_type,
     RegisterAuthenticationFactorCallback callback) {
   auto request = std::make_unique<TrustedVaultRequest>(
       TrustedVaultRequest::HttpMethod::kPost,
       GURL(trusted_vault_service_url_.spec() + kJoinSecurityDomainsURLPath),
       /*serialized_request_proto=*/
       CreateJoinSecurityDomainsRequest(last_trusted_vault_key_and_version,
-                                       public_key)
+                                       public_key, authentication_factor_type)
           .SerializeAsString());
 
   request->FetchAccessTokenAndSendRequest(
@@ -145,7 +167,8 @@
   auto request = std::make_unique<TrustedVaultRequest>(
       TrustedVaultRequest::HttpMethod::kGet,
       GURL(trusted_vault_service_url_.spec() +
-           kListSecurityDomainsURLPathAndQuery),
+           GetGetSecurityDomainMemberURLPathAndQuery(
+               device_key_pair->public_key().ExportToBytes())),
       /*serialized_request_proto=*/base::nullopt);
 
   request->FetchAccessTokenAndSendRequest(
diff --git a/components/sync/trusted_vault/trusted_vault_connection_impl.h b/components/sync/trusted_vault/trusted_vault_connection_impl.h
index 4acc86a..c9c6106 100644
--- a/components/sync/trusted_vault/trusted_vault_connection_impl.h
+++ b/components/sync/trusted_vault/trusted_vault_connection_impl.h
@@ -41,6 +41,7 @@
       const base::Optional<TrustedVaultKeyAndVersion>&
           last_trusted_vault_key_and_version,
       const SecureBoxPublicKey& authentication_factor_public_key,
+      AuthenticationFactorType authentication_factor_type,
       RegisterAuthenticationFactorCallback callback) override;
 
   std::unique_ptr<Request> DownloadNewKeys(
diff --git a/components/sync/trusted_vault/trusted_vault_connection_impl_unittest.cc b/components/sync/trusted_vault/trusted_vault_connection_impl_unittest.cc
index 526f4e5..9d783c4 100644
--- a/components/sync/trusted_vault/trusted_vault_connection_impl_unittest.cc
+++ b/components/sync/trusted_vault/trusted_vault_connection_impl_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/base64url.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
@@ -116,12 +117,14 @@
         /*content=*/std::string(), response_http_code);
   }
 
-  bool RespondToListSecurityDomainsRequest(
+  bool RespondToGetSecurityDomainMemberRequest(
       net::HttpStatusCode response_http_code) {
     // Allow request to reach |test_url_loader_factory_|.
     base::RunLoop().RunUntilIdle();
     return test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFullListSecurityDomainsURLForTesting(kTestURL).spec(),
+        GetFullGetSecurityDomainMemberURLForTesting(
+            kTestURL, MakeTestKeyPair()->public_key().ExportToBytes())
+            .spec(),
         /*content=*/std::string(), response_http_code);
   }
 
@@ -137,61 +140,119 @@
 };
 
 TEST_F(TrustedVaultConnectionImplTest, ShouldSendJoinSecurityDomainsRequest) {
-  const int kLastKeyVersion = 1234;
+  const TrustedVaultKeyAndVersion kTrustedVaultKeyAndVersion(kTrustedVaultKey,
+                                                             /*version=*/1234);
+  std::unique_ptr<SecureBoxKeyPair> key_pair = MakeTestKeyPair();
+  ASSERT_THAT(key_pair, NotNull());
 
+  std::unique_ptr<TrustedVaultConnection::Request> request =
+      connection()->RegisterAuthenticationFactor(
+          /*account_info=*/CoreAccountInfo(), kTrustedVaultKeyAndVersion,
+          key_pair->public_key(), AuthenticationFactorType::kPhysicalDevice,
+          TrustedVaultConnection::RegisterAuthenticationFactorCallback());
+  EXPECT_THAT(request, NotNull());
+
+  const network::TestURLLoaderFactory::PendingRequest* pending_request =
+      GetPendingHTTPRequest();
+  ASSERT_THAT(pending_request, NotNull());
+  const network::ResourceRequest& resource_request = pending_request->request;
+  EXPECT_THAT(resource_request.method, Eq("POST"));
+  EXPECT_THAT(resource_request.url,
+              Eq(GetFullJoinSecurityDomainsURLForTesting(kTestURL)));
+
+  sync_pb::JoinSecurityDomainsRequest deserialized_body;
+  EXPECT_TRUE(deserialized_body.ParseFromString(
+      network::GetUploadData(resource_request)));
+  EXPECT_THAT(deserialized_body.security_domain().name(),
+              Eq(kSyncSecurityDomainName));
+
+  std::string public_key_string;
+  AssignBytesToProtoString(key_pair->public_key().ExportToBytes(),
+                           &public_key_string);
+
+  std::string encoded_public_key;
+  base::Base64UrlEncode(public_key_string,
+                        base::Base64UrlEncodePolicy::OMIT_PADDING,
+                        &encoded_public_key);
+
+  const sync_pb::SecurityDomainMember& member =
+      deserialized_body.security_domain_member();
+  EXPECT_THAT(member.name(),
+              Eq(kSecurityDomainMemberNamePrefix + encoded_public_key));
+  EXPECT_THAT(member.public_key(), Eq(public_key_string));
+  EXPECT_THAT(member.member_type(),
+              Eq(sync_pb::SecurityDomainMember::MEMBER_TYPE_PHYSICAL_DEVICE));
+
+  const sync_pb::SharedMemberKey& shared_key =
+      deserialized_body.shared_member_key();
+  EXPECT_THAT(shared_key.epoch(), Eq(kTrustedVaultKeyAndVersion.version));
+
+  EXPECT_THAT(DecryptTrustedVaultWrappedKey(
+                  key_pair->private_key(),
+                  /*wrapped_key=*/ProtoStringToBytes(shared_key.wrapped_key())),
+              Eq(kTrustedVaultKeyAndVersion.key));
+  EXPECT_TRUE(VerifyTrustedVaultHMAC(
+      /*key=*/kTrustedVaultKeyAndVersion.key,
+      /*data=*/key_pair->public_key().ExportToBytes(),
+      /*digest=*/ProtoStringToBytes(shared_key.member_proof())));
+}
+
+TEST_F(TrustedVaultConnectionImplTest,
+       ShouldSendJoinSecurityDomainsRequestWithConstantKey) {
   std::unique_ptr<SecureBoxKeyPair> key_pair = MakeTestKeyPair();
   ASSERT_THAT(key_pair, NotNull());
 
   std::unique_ptr<TrustedVaultConnection::Request> request =
       connection()->RegisterAuthenticationFactor(
           /*account_info=*/CoreAccountInfo(),
-          TrustedVaultKeyAndVersion(kTrustedVaultKey, kLastKeyVersion),
-          key_pair->public_key(),
+          /*last_trusted_vault_key_and_version=*/base::nullopt,
+          key_pair->public_key(), AuthenticationFactorType::kPhysicalDevice,
           TrustedVaultConnection::RegisterAuthenticationFactorCallback());
   EXPECT_THAT(request, NotNull());
 
-  network::TestURLLoaderFactory::PendingRequest* pending_http_request =
+  const network::TestURLLoaderFactory::PendingRequest* pending_request =
       GetPendingHTTPRequest();
-  ASSERT_THAT(pending_http_request, NotNull());
-
-  const network::ResourceRequest& resource_request =
-      pending_http_request->request;
+  ASSERT_THAT(pending_request, NotNull());
+  const network::ResourceRequest& resource_request = pending_request->request;
   EXPECT_THAT(resource_request.method, Eq("POST"));
   EXPECT_THAT(resource_request.url,
               Eq(GetFullJoinSecurityDomainsURLForTesting(kTestURL)));
 
   sync_pb::JoinSecurityDomainsRequest deserialized_body;
-  deserialized_body.ParseFromString(network::GetUploadData(resource_request));
+  EXPECT_TRUE(deserialized_body.ParseFromString(
+      network::GetUploadData(resource_request)));
+  EXPECT_THAT(deserialized_body.security_domain().name(),
+              Eq(kSyncSecurityDomainName));
 
-  ASSERT_THAT(deserialized_body.security_domains(), SizeIs(1));
-  const sync_pb::SecurityDomain& security_domain =
-      deserialized_body.security_domains(0);
-  EXPECT_THAT(security_domain.name(), Eq(kSyncSecurityDomainName));
+  std::string public_key_string;
+  AssignBytesToProtoString(key_pair->public_key().ExportToBytes(),
+                           &public_key_string);
 
-  ASSERT_THAT(security_domain.members(), SizeIs(1));
-  const sync_pb::SecurityDomain::Member& member = security_domain.members(0);
-  EXPECT_THAT(ProtoStringToBytes(member.public_key()),
-              Eq(key_pair->public_key().ExportToBytes()));
+  std::string encoded_public_key;
+  base::Base64UrlEncode(public_key_string,
+                        base::Base64UrlEncodePolicy::OMIT_PADDING,
+                        &encoded_public_key);
 
-  ASSERT_THAT(member.keys(), SizeIs(1));
-  const sync_pb::SharedKey& shared_key = member.keys(0);
-  EXPECT_THAT(shared_key.epoch(), Eq(kLastKeyVersion));
+  const sync_pb::SecurityDomainMember& member =
+      deserialized_body.security_domain_member();
+  EXPECT_THAT(member.name(),
+              Eq(kSecurityDomainMemberNamePrefix + encoded_public_key));
+  EXPECT_THAT(member.public_key(), Eq(public_key_string));
+  EXPECT_THAT(member.member_type(),
+              Eq(sync_pb::SecurityDomainMember::MEMBER_TYPE_PHYSICAL_DEVICE));
 
-  base::Optional<std::vector<uint8_t>> decrypted_trusted_vault_key =
-      DecryptTrustedVaultWrappedKey(
-          key_pair->private_key(),
-          /*wrapped_key=*/ProtoStringToBytes(shared_key.wrapped_key()));
-  ASSERT_THAT(decrypted_trusted_vault_key, Ne(base::nullopt));
-  EXPECT_THAT(*decrypted_trusted_vault_key, Eq(kTrustedVaultKey));
+  const sync_pb::SharedMemberKey& shared_key =
+      deserialized_body.shared_member_key();
+  EXPECT_FALSE(shared_key.has_epoch());
 
-  // HMAC_SHA256 result using |key_pair| public key as message and
-  // kTrustedVaultKey as a secret key.
-  const std::string kHexEncodedExpectedMemberProof =
-      "247b19e1a835dff90405b349413d51a8a9c3c10035772cf5d60e9403053e67fd";
-  std::string expected_member_proof;
-  ASSERT_TRUE(base::HexStringToString(kHexEncodedExpectedMemberProof,
-                                      &expected_member_proof));
-  EXPECT_THAT(shared_key.member_proof(), Eq(expected_member_proof));
+  EXPECT_THAT(DecryptTrustedVaultWrappedKey(
+                  key_pair->private_key(),
+                  /*wrapped_key=*/ProtoStringToBytes(shared_key.wrapped_key())),
+              Eq(GetConstantTrustedVaultKey()));
+  EXPECT_TRUE(VerifyTrustedVaultHMAC(
+      /*key=*/GetConstantTrustedVaultKey(),
+      /*data=*/key_pair->public_key().ExportToBytes(),
+      /*digest=*/ProtoStringToBytes(shared_key.member_proof())));
 }
 
 TEST_F(TrustedVaultConnectionImplTest,
@@ -207,7 +268,8 @@
       connection()->RegisterAuthenticationFactor(
           /*account_info=*/CoreAccountInfo(),
           TrustedVaultKeyAndVersion(kTrustedVaultKey, /*version=*/1),
-          key_pair->public_key(), callback.Get());
+          key_pair->public_key(), AuthenticationFactorType::kPhysicalDevice,
+          callback.Get());
   ASSERT_THAT(request, NotNull());
 
   EXPECT_CALL(callback, Run(Eq(TrustedVaultRequestStatus::kSuccess)));
@@ -227,7 +289,8 @@
       connection()->RegisterAuthenticationFactor(
           /*account_info=*/CoreAccountInfo(),
           TrustedVaultKeyAndVersion(kTrustedVaultKey, /*version=*/1),
-          key_pair->public_key(), callback.Get());
+          key_pair->public_key(), AuthenticationFactorType::kPhysicalDevice,
+          callback.Get());
   ASSERT_THAT(request, NotNull());
 
   EXPECT_CALL(callback, Run(Eq(TrustedVaultRequestStatus::kOtherError)));
@@ -236,7 +299,7 @@
 }
 
 TEST_F(TrustedVaultConnectionImplTest,
-       ShouldHandleFailedJoinSecurityDomainsRequestWithBadRequestStatus) {
+       ShouldHandleFailedJoinSecurityDomainsRequestWithNotFoundStatus) {
   std::unique_ptr<SecureBoxKeyPair> key_pair = MakeTestKeyPair();
   ASSERT_THAT(key_pair, NotNull());
 
@@ -248,14 +311,39 @@
       connection()->RegisterAuthenticationFactor(
           /*account_info=*/CoreAccountInfo(),
           TrustedVaultKeyAndVersion(kTrustedVaultKey, /*version=*/1),
-          key_pair->public_key(), callback.Get());
+          key_pair->public_key(), AuthenticationFactorType::kPhysicalDevice,
+          callback.Get());
   ASSERT_THAT(request, NotNull());
 
-  // In particular, HTTP_BAD_REQUEST indicates that
+  // In particular, HTTP_NOT_FOUND indicates that security domain was removed.
+  EXPECT_CALL(callback, Run(Eq(TrustedVaultRequestStatus::kLocalDataObsolete)));
+  EXPECT_TRUE(RespondToJoinSecurityDomainsRequest(net::HTTP_NOT_FOUND));
+}
+
+TEST_F(
+    TrustedVaultConnectionImplTest,
+    ShouldHandleFailedJoinSecurityDomainsRequestWithPreconditionFailedStatus) {
+  std::unique_ptr<SecureBoxKeyPair> key_pair = MakeTestKeyPair();
+  ASSERT_THAT(key_pair, NotNull());
+
+  base::MockCallback<
+      TrustedVaultConnection::RegisterAuthenticationFactorCallback>
+      callback;
+
+  std::unique_ptr<TrustedVaultConnection::Request> request =
+      connection()->RegisterAuthenticationFactor(
+          /*account_info=*/CoreAccountInfo(),
+          TrustedVaultKeyAndVersion(kTrustedVaultKey, /*version=*/1),
+          key_pair->public_key(), AuthenticationFactorType::kPhysicalDevice,
+          callback.Get());
+  ASSERT_THAT(request, NotNull());
+
+  // In particular, HTTP_PRECONDITION_FAILED indicates that
   // |last_trusted_vault_key_and_version| is not actually the last on the server
   // side.
   EXPECT_CALL(callback, Run(Eq(TrustedVaultRequestStatus::kLocalDataObsolete)));
-  EXPECT_TRUE(RespondToJoinSecurityDomainsRequest(net::HTTP_BAD_REQUEST));
+  EXPECT_TRUE(
+      RespondToJoinSecurityDomainsRequest(net::HTTP_PRECONDITION_FAILED));
 }
 
 TEST_F(
@@ -279,7 +367,8 @@
       connection->RegisterAuthenticationFactor(
           /*account_info=*/CoreAccountInfo(),
           TrustedVaultKeyAndVersion(kTrustedVaultKey, /*version=*/1),
-          key_pair->public_key(), callback.Get());
+          key_pair->public_key(), AuthenticationFactorType::kPhysicalDevice,
+          callback.Get());
   ASSERT_THAT(request, NotNull());
 
   // No requests should be sent to the network.
@@ -298,7 +387,8 @@
       connection()->RegisterAuthenticationFactor(
           /*account_info=*/CoreAccountInfo(),
           TrustedVaultKeyAndVersion(kTrustedVaultKey, /*version=*/1),
-          key_pair->public_key(), callback.Get());
+          key_pair->public_key(), AuthenticationFactorType::kPhysicalDevice,
+          callback.Get());
   ASSERT_THAT(request, NotNull());
 
   EXPECT_CALL(callback, Run).Times(0);
@@ -325,7 +415,8 @@
       pending_http_request->request;
   EXPECT_THAT(resource_request.method, Eq("GET"));
   EXPECT_THAT(resource_request.url,
-              Eq(GetFullListSecurityDomainsURLForTesting(kTestURL)));
+              Eq(GetFullGetSecurityDomainMemberURLForTesting(
+                  kTestURL, MakeTestKeyPair()->public_key().ExportToBytes())));
 }
 
 // TODO(crbug.com/1113598): add coverage for at least one successful case
@@ -345,7 +436,7 @@
 
   EXPECT_CALL(callback, Run(Eq(TrustedVaultRequestStatus::kOtherError), _, _));
   EXPECT_TRUE(
-      RespondToListSecurityDomainsRequest(net::HTTP_INTERNAL_SERVER_ERROR));
+      RespondToGetSecurityDomainMemberRequest(net::HTTP_INTERNAL_SERVER_ERROR));
 }
 
 TEST_F(TrustedVaultConnectionImplTest,
@@ -388,7 +479,7 @@
   request.reset();
   // Returned value isn't checked here, because the request can be cancelled
   // before reaching TestURLLoaderFactory.
-  RespondToListSecurityDomainsRequest(net::HTTP_OK);
+  RespondToGetSecurityDomainMemberRequest(net::HTTP_OK);
 }
 
 }  // namespace
diff --git a/components/sync/trusted_vault/trusted_vault_request.cc b/components/sync/trusted_vault/trusted_vault_request.cc
index f6b5af2..4904aab 100644
--- a/components/sync/trusted_vault/trusted_vault_request.cc
+++ b/components/sync/trusted_vault/trusted_vault_request.cc
@@ -120,10 +120,13 @@
   if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers) {
     http_response_code = url_loader_->ResponseInfo()->headers->response_code();
   }
-  if (http_response_code == net::HTTP_BAD_REQUEST) {
-    // Bad request can indicate client-side data being obsolete, distinguish it
-    // to allow API users to decide how to handle.
-    RunCompletionCallbackAndMaybeDestroySelf(HttpStatus::kBadRequest,
+  if (http_response_code == net::HTTP_NOT_FOUND) {
+    RunCompletionCallbackAndMaybeDestroySelf(HttpStatus::kNotFound,
+                                             std::string());
+    return;
+  }
+  if (http_response_code == net::HTTP_PRECONDITION_FAILED) {
+    RunCompletionCallbackAndMaybeDestroySelf(HttpStatus::kFailedPrecondition,
                                              std::string());
     return;
   }
diff --git a/components/sync/trusted_vault/trusted_vault_request.h b/components/sync/trusted_vault/trusted_vault_request.h
index ea57f50..d79956b 100644
--- a/components/sync/trusted_vault/trusted_vault_request.h
+++ b/components/sync/trusted_vault/trusted_vault_request.h
@@ -35,10 +35,12 @@
   enum class HttpStatus {
     // Reported when server returns http status code 200 or 204.
     kSuccess,
-    // Reported when server return http status code 400 (bad request).
-    kBadRequest,
+    // Reported when server returns http status code 404 (not found).
+    kNotFound,
+    // Reported when server returns http status code 412 (precondition failed).
+    kFailedPrecondition,
     // Reported when other error occurs: unable to fetch access token, network
-    // and http errors (except 400).
+    // and http errors (except 404 and 412).
     kOtherError
   };
 
diff --git a/components/sync/trusted_vault/trusted_vault_request_unittest.cc b/components/sync/trusted_vault/trusted_vault_request_unittest.cc
index 54161b06..0b4b320 100644
--- a/components/sync/trusted_vault/trusted_vault_request_unittest.cc
+++ b/components/sync/trusted_vault/trusted_vault_request_unittest.cc
@@ -227,7 +227,7 @@
                                    /*response_body=*/""));
 }
 
-TEST_F(TrustedVaultRequestTest, ShouldHandleBadRequestStatus) {
+TEST_F(TrustedVaultRequestTest, ShouldHandleNotFoundStatus) {
   base::MockCallback<TrustedVaultRequest::CompletionCallback>
       completion_callback;
   std::unique_ptr<TrustedVaultRequest> request = StartNewRequestWithAccessToken(
@@ -236,8 +236,22 @@
 
   // |completion_callback| should be called after receiving response.
   EXPECT_CALL(completion_callback,
-              Run(TrustedVaultRequest::HttpStatus::kBadRequest, _));
-  EXPECT_TRUE(RespondToHttpRequest(net::OK, net::HTTP_BAD_REQUEST,
+              Run(TrustedVaultRequest::HttpStatus::kNotFound, _));
+  EXPECT_TRUE(RespondToHttpRequest(net::OK, net::HTTP_NOT_FOUND,
+                                   /*response_body=*/""));
+}
+
+TEST_F(TrustedVaultRequestTest, ShouldHandlePreconditionFailedStatus) {
+  base::MockCallback<TrustedVaultRequest::CompletionCallback>
+      completion_callback;
+  std::unique_ptr<TrustedVaultRequest> request = StartNewRequestWithAccessToken(
+      kAccessToken, TrustedVaultRequest::HttpMethod::kGet,
+      /*request_body=*/base::nullopt, completion_callback.Get());
+
+  // |completion_callback| should be called after receiving response.
+  EXPECT_CALL(completion_callback,
+              Run(TrustedVaultRequest::HttpStatus::kFailedPrecondition, _));
+  EXPECT_TRUE(RespondToHttpRequest(net::OK, net::HTTP_PRECONDITION_FAILED,
                                    /*response_body=*/""));
 }
 
diff --git a/components/sync/trusted_vault/trusted_vault_server_constants.cc b/components/sync/trusted_vault/trusted_vault_server_constants.cc
index 01d5186..8d00029 100644
--- a/components/sync/trusted_vault/trusted_vault_server_constants.cc
+++ b/components/sync/trusted_vault/trusted_vault_server_constants.cc
@@ -4,6 +4,7 @@
 
 #include "components/sync/trusted_vault/trusted_vault_server_constants.h"
 
+#include "base/base64url.h"
 #include "net/base/url_util.h"
 
 namespace syncer {
@@ -15,23 +16,36 @@
 
 }  // namespace
 
-const char kSyncSecurityDomainName[] = "chromesync";
-const char kJoinSecurityDomainsURLPath[] = "/domain:join";
-const char kListSecurityDomainsURLPathAndQuery[] = "/domain:list?view=1";
+const char kSyncSecurityDomainName[] = "users/me/securitydomains/chromesync";
+const char kSecurityDomainMemberNamePrefix[] = "users/me/members/";
+const char kJoinSecurityDomainsURLPath[] =
+    "users/me/securitydomains/chromesync:join";
 
 std::vector<uint8_t> GetConstantTrustedVaultKey() {
   return std::vector<uint8_t>(16, 0);
 }
 
+std::string GetGetSecurityDomainMemberURLPathAndQuery(
+    base::span<const uint8_t> public_key) {
+  std::string encoded_public_key;
+  base::Base64UrlEncode(std::string(public_key.begin(), public_key.end()),
+                        base::Base64UrlEncodePolicy::OMIT_PADDING,
+                        &encoded_public_key);
+  return kSecurityDomainMemberNamePrefix + encoded_public_key + "?view=2";
+}
+
 GURL GetFullJoinSecurityDomainsURLForTesting(const GURL& server_url) {
   return net::AppendQueryParameter(
       /*url=*/GURL(server_url.spec() + kJoinSecurityDomainsURLPath),
       kQueryParameterAlternateOutputKey, kQueryParameterAlternateOutputProto);
 }
 
-GURL GetFullListSecurityDomainsURLForTesting(const GURL& server_url) {
+GURL GetFullGetSecurityDomainMemberURLForTesting(
+    const GURL& server_url,
+    base::span<const uint8_t> public_key) {
   return net::AppendQueryParameter(
-      /*url=*/GURL(server_url.spec() + kListSecurityDomainsURLPathAndQuery),
+      /*url=*/GURL(server_url.spec() +
+                   GetGetSecurityDomainMemberURLPathAndQuery(public_key)),
       kQueryParameterAlternateOutputKey, kQueryParameterAlternateOutputProto);
 }
 
diff --git a/components/sync/trusted_vault/trusted_vault_server_constants.h b/components/sync/trusted_vault/trusted_vault_server_constants.h
index d9276ff..c3cc17d 100644
--- a/components/sync/trusted_vault/trusted_vault_server_constants.h
+++ b/components/sync/trusted_vault/trusted_vault_server_constants.h
@@ -5,22 +5,27 @@
 #ifndef COMPONENTS_SYNC_TRUSTED_VAULT_TRUSTED_VAULT_SERVER_CONSTANTS_H_
 #define COMPONENTS_SYNC_TRUSTED_VAULT_TRUSTED_VAULT_SERVER_CONSTANTS_H_
 
+#include <string>
 #include <vector>
 
+#include "base/containers/span.h"
 #include "url/gurl.h"
 
 namespace syncer {
 
 extern const char kSyncSecurityDomainName[];
+extern const char kSecurityDomainMemberNamePrefix[];
 extern const char kJoinSecurityDomainsURLPath[];
-extern const char kListSecurityDomainsURLPathAndQuery[];
 
 std::vector<uint8_t> GetConstantTrustedVaultKey();
+std::string GetGetSecurityDomainMemberURLPathAndQuery(
+    base::span<const uint8_t> public_key);
 
 // Computes full URL, including alternate proto param.
 GURL GetFullJoinSecurityDomainsURLForTesting(const GURL& server_url);
-GURL GetFullListSecurityDomainsURLForTesting(const GURL& server_url);
-
+GURL GetFullGetSecurityDomainMemberURLForTesting(
+    const GURL& server_url,
+    base::span<const uint8_t> public_key);
 }  // namespace syncer
 
 #endif  // COMPONENTS_SYNC_TRUSTED_VAULT_TRUSTED_VAULT_SERVER_CONSTANTS_H_
diff --git a/components/sync_ui_strings.grdp b/components/sync_ui_strings.grdp
index c475131..d8a948c 100644
--- a/components/sync_ui_strings.grdp
+++ b/components/sync_ui_strings.grdp
@@ -2,13 +2,13 @@
 <grit-part>
   <if expr="not is_android">
     <message name="IDS_SYNC_BASIC_ENCRYPTION_DATA" desc="Text of the radio that when selected enables basic encryption.">
-      Encrypt synced passwords with your Google credentials
+      Encrypt synced passwords with your Google Account
     </message>
     <message name="IDS_SYNC_CONFIGURE_ENCRYPTION" desc="Link to configure sync encryption for passwords">
       Please update your sync passphrase.
     </message>
     <message name="IDS_SYNC_DATATYPE_AUTOFILL" desc="Form Autofill items, one of the data types that we allow syncing.">
-      Autofill
+      Addresses and More
     </message>
     <message name="IDS_SYNC_DATATYPE_BOOKMARKS" desc="Bookmarks, one of the data types that we allow syncing.">
       Bookmarks
@@ -20,7 +20,7 @@
       Settings
     </message>
     <message name="IDS_SYNC_DATATYPE_TABS" desc="Open Tabs, one of the data types that we allow syncing.">
-      Open tabs
+      Open Tabs
     </message>
     <message name="IDS_SYNC_DATATYPE_TYPED_URLS" desc="History, one of the data types that we allow syncing.">
       History
diff --git a/components/sync_ui_strings_grdp/IDS_SYNC_BASIC_ENCRYPTION_DATA.png.sha1 b/components/sync_ui_strings_grdp/IDS_SYNC_BASIC_ENCRYPTION_DATA.png.sha1
new file mode 100644
index 0000000..5746730
--- /dev/null
+++ b/components/sync_ui_strings_grdp/IDS_SYNC_BASIC_ENCRYPTION_DATA.png.sha1
@@ -0,0 +1 @@
+50870c0a1bb32653afc3d444273b6009245115c4
\ No newline at end of file
diff --git a/components/sync_ui_strings_grdp/IDS_SYNC_DATATYPE_AUTOFILL.png.sha1 b/components/sync_ui_strings_grdp/IDS_SYNC_DATATYPE_AUTOFILL.png.sha1
new file mode 100644
index 0000000..121f2d7
--- /dev/null
+++ b/components/sync_ui_strings_grdp/IDS_SYNC_DATATYPE_AUTOFILL.png.sha1
@@ -0,0 +1 @@
+262131d038779e9b6c969e7d266d85d7c6190067
\ No newline at end of file
diff --git a/components/sync_ui_strings_grdp/IDS_SYNC_DATATYPE_TABS.png.sha1 b/components/sync_ui_strings_grdp/IDS_SYNC_DATATYPE_TABS.png.sha1
new file mode 100644
index 0000000..121f2d7
--- /dev/null
+++ b/components/sync_ui_strings_grdp/IDS_SYNC_DATATYPE_TABS.png.sha1
@@ -0,0 +1 @@
+262131d038779e9b6c969e7d266d85d7c6190067
\ No newline at end of file
diff --git a/components/system_media_controls/mac/now_playing_info_center_delegate.h b/components/system_media_controls/mac/now_playing_info_center_delegate.h
index 850161d5..f8f5fad 100644
--- a/components/system_media_controls/mac/now_playing_info_center_delegate.h
+++ b/components/system_media_controls/mac/now_playing_info_center_delegate.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_SYSTEM_MEDIA_CONTROLS_MAC_NOW_PLAYING_INFO_CENTER_DELEGATE_H_
 
 #include "base/mac/scoped_nsobject.h"
+#include "base/timer/timer.h"
 #include "components/system_media_controls/system_media_controls.h"
 
 @class NowPlayingInfoCenterDelegateCocoa;
@@ -31,6 +32,25 @@
   void ClearMetadata();
 
  private:
+  // Starts timer to update the current playback status and position. This
+  // debounce timer is mandatory as the now playing widget doesn't work properly
+  // when updated multiple times in a row.
+  void StartTimer();
+
+  // Updates the current playback status and position depending on most recently
+  // received playback status and position.
+  void UpdatePlaybackStatusAndPosition();
+
+  // Stores the most recently received playback status.
+  base::Optional<SystemMediaControls::PlaybackStatus> playback_status_;
+
+  // Stores the most recently received position.
+  base::Optional<media_session::MediaPosition> position_;
+
+  // Calls UpdatePlaybackStatusAndPosition() when the timer expires.
+  std::unique_ptr<base::OneShotTimer> timer_ =
+      std::make_unique<base::OneShotTimer>();
+
   base::scoped_nsobject<NowPlayingInfoCenterDelegateCocoa>
       now_playing_info_center_delegate_cocoa_;
 };
diff --git a/components/system_media_controls/mac/now_playing_info_center_delegate.mm b/components/system_media_controls/mac/now_playing_info_center_delegate.mm
index 4c66ddd5..82a3a10b 100644
--- a/components/system_media_controls/mac/now_playing_info_center_delegate.mm
+++ b/components/system_media_controls/mac/now_playing_info_center_delegate.mm
@@ -6,6 +6,7 @@
 
 #import <MediaPlayer/MediaPlayer.h>
 
+#include "base/bind.h"
 #include "base/notreached.h"
 #include "base/strings/sys_string_conversions.h"
 #include "components/system_media_controls/mac/now_playing_info_center_delegate_cocoa.h"
@@ -40,52 +41,88 @@
 
 NowPlayingInfoCenterDelegate::~NowPlayingInfoCenterDelegate() {
   [now_playing_info_center_delegate_cocoa_ resetNowPlayingInfo];
+  timer_->Stop();
 }
 
 void NowPlayingInfoCenterDelegate::SetPlaybackStatus(
     SystemMediaControls::PlaybackStatus status) {
-  MPNowPlayingPlaybackState state =
-      PlaybackStatusToMPNowPlayingPlaybackState(status);
-  [now_playing_info_center_delegate_cocoa_ setPlaybackState:state];
+  playback_status_ = status;
+  StartTimer();
 }
 
 void NowPlayingInfoCenterDelegate::SetTitle(const base::string16& title) {
   [now_playing_info_center_delegate_cocoa_
       setTitle:base::SysUTF16ToNSString(title)];
+  [now_playing_info_center_delegate_cocoa_ updateNowPlayingInfo];
 }
 
 void NowPlayingInfoCenterDelegate::SetArtist(const base::string16& artist) {
   [now_playing_info_center_delegate_cocoa_
       setArtist:base::SysUTF16ToNSString(artist)];
+  [now_playing_info_center_delegate_cocoa_ updateNowPlayingInfo];
 }
 
 void NowPlayingInfoCenterDelegate::SetAlbum(const base::string16& album) {
   [now_playing_info_center_delegate_cocoa_
       setAlbum:base::SysUTF16ToNSString(album)];
+  [now_playing_info_center_delegate_cocoa_ updateNowPlayingInfo];
 }
 
 void NowPlayingInfoCenterDelegate::SetPosition(
     const media_session::MediaPosition& position) {
+  position_ = position;
+  StartTimer();
+}
+
+void NowPlayingInfoCenterDelegate::StartTimer() {
+  timer_->Start(
+      FROM_HERE, base::TimeDelta::FromMilliseconds(100),
+      base::BindOnce(
+          &NowPlayingInfoCenterDelegate::UpdatePlaybackStatusAndPosition,
+          base::Unretained(this)));
+}
+
+void NowPlayingInfoCenterDelegate::UpdatePlaybackStatusAndPosition() {
+  auto position = position_.value_or(media_session::MediaPosition(
+      0 /* playback_rate */, base::TimeDelta::FromSeconds(0) /* duration */,
+      base::TimeDelta::FromSeconds(0) /* position */));
+  auto playback_status =
+      playback_status_.value_or(SystemMediaControls::PlaybackStatus::kStopped);
+
+  MPNowPlayingPlaybackState state =
+      PlaybackStatusToMPNowPlayingPlaybackState(playback_status);
+  [now_playing_info_center_delegate_cocoa_ setPlaybackState:state];
+
   auto time_since_epoch =
       position.last_updated_time() - base::TimeTicks::UnixEpoch();
-
-  [now_playing_info_center_delegate_cocoa_
-      setPlaybackRate:[NSNumber numberWithDouble:position.playback_rate()]];
   [now_playing_info_center_delegate_cocoa_
       setCurrentPlaybackDate:
           [NSDate dateWithTimeIntervalSince1970:time_since_epoch.InSecondsF()]];
   [now_playing_info_center_delegate_cocoa_
+      setDuration:[NSNumber numberWithFloat:position.duration().InSecondsF()]];
+
+  // If we're not currently playing, then set the rate to zero.
+  double rate =
+      (playback_status == SystemMediaControls::PlaybackStatus::kPlaying)
+          ? position.playback_rate()
+          : 0;
+  [now_playing_info_center_delegate_cocoa_
+      setPlaybackRate:[NSNumber numberWithDouble:rate]];
+  [now_playing_info_center_delegate_cocoa_
       setElapsedPlaybackTime:
           [NSNumber numberWithFloat:position
                                         .GetPositionAtTime(
                                             position.last_updated_time())
                                         .InSecondsF()]];
-  [now_playing_info_center_delegate_cocoa_
-      setDuration:[NSNumber numberWithFloat:position.duration().InSecondsF()]];
+
+  [now_playing_info_center_delegate_cocoa_ updateNowPlayingInfo];
 }
 
 void NowPlayingInfoCenterDelegate::ClearMetadata() {
   [now_playing_info_center_delegate_cocoa_ clearMetadata];
+  playback_status_.reset();
+  position_.reset();
+  timer_->Stop();
 }
 
 }  // namespace internal
diff --git a/components/system_media_controls/mac/now_playing_info_center_delegate_cocoa.h b/components/system_media_controls/mac/now_playing_info_center_delegate_cocoa.h
index 6d9dd981..95536ab 100644
--- a/components/system_media_controls/mac/now_playing_info_center_delegate_cocoa.h
+++ b/components/system_media_controls/mac/now_playing_info_center_delegate_cocoa.h
@@ -25,6 +25,7 @@
 - (void)setCurrentPlaybackDate:(NSDate*)date;
 - (void)setElapsedPlaybackTime:(NSNumber*)time;
 - (void)setDuration:(NSNumber*)duration;
+- (void)updateNowPlayingInfo;
 
 // Sets all metadata to default values.
 - (void)clearMetadata;
diff --git a/components/system_media_controls/mac/now_playing_info_center_delegate_cocoa.mm b/components/system_media_controls/mac/now_playing_info_center_delegate_cocoa.mm
index 45c80b5..1b27fcd 100644
--- a/components/system_media_controls/mac/now_playing_info_center_delegate_cocoa.mm
+++ b/components/system_media_controls/mac/now_playing_info_center_delegate_cocoa.mm
@@ -40,45 +40,37 @@
 
 - (void)setPlaybackState:(MPNowPlayingPlaybackState)state {
   [MPNowPlayingInfoCenter defaultCenter].playbackState = state;
-  [self updateNowPlayingInfo];
 }
 
 - (void)setTitle:(NSString*)title {
   [_nowPlayingInfo setObject:title forKey:MPMediaItemPropertyTitle];
-  [self updateNowPlayingInfo];
 }
 
 - (void)setArtist:(NSString*)artist {
   [_nowPlayingInfo setObject:artist forKey:MPMediaItemPropertyArtist];
-  [self updateNowPlayingInfo];
 }
 
 - (void)setAlbum:(NSString*)album {
   [_nowPlayingInfo setObject:album forKey:MPMediaItemPropertyAlbumTitle];
-  [self updateNowPlayingInfo];
 }
 
 - (void)setPlaybackRate:(NSNumber*)rate {
   [_nowPlayingInfo setObject:rate forKey:MPNowPlayingInfoPropertyPlaybackRate];
-  [self updateNowPlayingInfo];
 }
 
 - (void)setCurrentPlaybackDate:(NSDate*)date {
   [_nowPlayingInfo setObject:date
                       forKey:MPNowPlayingInfoPropertyCurrentPlaybackDate];
-  [self updateNowPlayingInfo];
 }
 
 - (void)setElapsedPlaybackTime:(NSNumber*)time {
   [_nowPlayingInfo setObject:time
                       forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
-  [self updateNowPlayingInfo];
 }
 
 - (void)setDuration:(NSNumber*)duration {
   [_nowPlayingInfo setObject:duration
                       forKey:MPMediaItemPropertyPlaybackDuration];
-  [self updateNowPlayingInfo];
 }
 
 - (void)clearMetadata {
diff --git a/components/translate/core/language_detection/language_detection_model.cc b/components/translate/core/language_detection/language_detection_model.cc
index 2662665..f182c77b 100644
--- a/components/translate/core/language_detection/language_detection_model.cc
+++ b/components/translate/core/language_detection/language_detection_model.cc
@@ -27,6 +27,14 @@
 // experimentation.
 constexpr float kDefaultReliabilityThreshold = .7;
 
+// The number of characters to sample and provide as a buffer to the model
+// for determining its language.
+constexpr int kTextSampleLength = 250;
+
+// The number of samples of |kTextSampleLength| to evaluate the model when
+// determining the language of the page content.
+constexpr int kNumTextSamples = 3;
+
 }  // namespace
 
 namespace {
@@ -94,6 +102,19 @@
   return lang_detection_model_ != nullptr;
 }
 
+std::pair<std::string, float> LanguageDetectionModel::DetectTopLanguage(
+    const std::string& sampled_str) const {
+  DCHECK(IsAvailable());
+  std::vector<tflite::task::core::Category> categories =
+      lang_detection_model_->Classify(sampled_str);
+  std::sort(categories.begin(), categories.end(), sort_category());
+
+  if (categories.empty())
+    return std::make_pair(translate::kUnknownLanguageCode, 0.0);
+
+  return std::make_pair(categories[0].class_name, categories[0].score);
+}
+
 std::string LanguageDetectionModel::DeterminePageLanguage(
     const std::string& code,
     const std::string& html_lang,
@@ -113,19 +134,39 @@
   if (!lang_detection_model_)
     return translate::kUnknownLanguageCode;
 
-  std::vector<tflite::task::core::Category> categories =
-      lang_detection_model_->Classify(base::UTF16ToUTF8(contents));
-  std::sort(categories.begin(), categories.end(), sort_category());
+  std::vector<std::pair<std::string, float>> model_predictions;
+  // First evaluate the model on the entire contents based on the model's
+  // implementation, for v1 it is the first 128 tokens that are unicode
+  // "letters". We do not need to have the model's length in sync with
+  // the sampling logic for v1 as 128 tokens is unlikely to be changed.
+  model_predictions.emplace_back(
+      DetectTopLanguage(base::UTF16ToUTF8(contents)));
+  if (contents.length() > kNumTextSamples * kTextSampleLength) {
+    // Strings with UTF-8 have different widths so substr should be performed on
+    // the UTF16 strings to ensure alignment and then convert down to UTF-8
+    // strings for model evaluation.
+    std::string sampled_str = base::UTF16ToUTF8(contents.substr(
+        contents.length() - kTextSampleLength, kTextSampleLength));
+    // Evaluate on the last |kTextSampleLength| characters.
+    model_predictions.emplace_back(DetectTopLanguage(sampled_str));
 
-  if (categories.empty())
-    return translate::kUnknownLanguageCode;
+    // Sample and evaluate on the middle |kTextSampleLength| characters.
+    sampled_str = base::UTF16ToUTF8(
+        contents.substr(contents.length() / 2, kTextSampleLength));
+    model_predictions.emplace_back(DetectTopLanguage(sampled_str));
+  }
 
-  prediction_reliability_score = categories[0].score;
+  const auto top_language_result = std::max_element(
+      model_predictions.begin(), model_predictions.end(),
+      [](auto& left, auto& right) { return left.second < right.second; });
+
+  prediction_reliability_score = top_language_result->second;
+
   bool is_reliable =
       prediction_reliability_score > kDefaultReliabilityThreshold;
 
   std::string final_prediction = translate::FilterDetectedLanguage(
-      base::UTF16ToUTF8(contents), categories[0].class_name, is_reliable);
+      base::UTF16ToUTF8(contents), top_language_result->first, is_reliable);
   *predicted_language = final_prediction;
   *is_prediction_reliable = is_reliable;
   language::ToTranslateLanguageSynonym(&final_prediction);
@@ -141,4 +182,4 @@
   return kTFLiteModelVersion;
 }
 
-}  // namespace translate
\ No newline at end of file
+}  // namespace translate
diff --git a/components/translate/core/language_detection/language_detection_model.h b/components/translate/core/language_detection/language_detection_model.h
index 8115662..437c24a 100644
--- a/components/translate/core/language_detection/language_detection_model.h
+++ b/components/translate/core/language_detection/language_detection_model.h
@@ -65,6 +65,11 @@
   std::string GetModelVersion() const;
 
  private:
+  // Execute the model on the provided |sampled_str| and return the top language
+  // and the models score/confidence in that prediction.
+  std::pair<std::string, float> DetectTopLanguage(
+      const std::string& sampled_str) const;
+
   // A memory-mapped file that contains the TFLite model used for
   // determining the language of a page. This must be valid in order
   // to evaluate the model owned by |this|.
diff --git a/components/translate/core/language_detection/language_detection_model_unittest.cc b/components/translate/core/language_detection/language_detection_model_unittest.cc
index c836adb..1729e97 100644
--- a/components/translate/core/language_detection/language_detection_model_unittest.cc
+++ b/components/translate/core/language_detection/language_detection_model_unittest.cc
@@ -113,4 +113,68 @@
       "LanguageDetection.TFLite.DidAttemptDetection", true, 1);
 }
 
+TEST(LanguageDetectionModelTest, LongTextLanguageDetemination) {
+  base::HistogramTester histogram_tester;
+  base::File file = GetValidModelFile();
+  LanguageDetectionModel language_detection_model;
+  language_detection_model.UpdateWithFile(std::move(file));
+  EXPECT_TRUE(language_detection_model.IsAvailable());
+
+  bool is_prediction_reliable;
+  float model_reliability_score = 0.0;
+  std::string predicted_language;
+  const char* const zh_content_string =
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "对于要提交的图书 我确认 我是版权所有者或已得到版权所有者的授权 "
+      "要更改您的国家 地区 请在此表的最上端更改您的"
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "对于要提交的图书 我确认 我是版权所有者或已得到版权所有者的授权 "
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "对于要提交的图书 我确认 我是版权所有者或已得到版权所有者的授权 "
+      "要更改您的国家 地区 请在此表的最上端更改您的"
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "对于要提交的图书 我确认 我是版权所有者或已得到版权所有者的授权 "
+      "要更改您的国家 地区 请在此表的最上端更改您的"
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "对于要提交的图书 我确认 我是版权所有者或已得到版权所有者的授权 "
+      "要更改您的国家 地区 请在此表的最上端更改您的"
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "对于要提交的图书 我确认 我是版权所有者或已得到版权所有者的授权 "
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "对于要提交的图书 我确认 我是版权所有者或已得到版权所有者的授权 "
+      "要更改您的国家 地区 请在此表的最上端更改您的"
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "对于要提交的图书 我确认 我是版权所有者或已得到版权所有者的授权 "
+      "要更改您的国家 地区 请在此表的最上端更改您的"
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "要更改您的国家 地区 请在此表的最上端更改您的"
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "对于要提交的图书 我确认 我是版权所有者或已得到版权所有者的授权 "
+      "要更改您的国家 地区 请在此表的最上端更改您的"
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "对于要提交的图书 我确认 我是版权所有者或已得到版权所有者的授权 "
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "对于要提交的图书 我确认 我是版权所有者或已得到版权所有者的授权 "
+      "要更改您的国家 地区 请在此表的最上端更改您的"
+      "This is a page apparently written in English."
+      "This is a page apparently written in English."
+      "This is a page apparently written in English."
+      "要更改您的国家 地区 请在此表的最上端更改您的"
+      "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 "
+      "要更改您的国家 地区 请在此表的最上端更改您的";
+
+  base::string16 contents = base::UTF8ToUTF16(zh_content_string);
+  EXPECT_GE(contents.length(), 250u * 3u);
+  std::string language = language_detection_model.DeterminePageLanguage(
+      std::string("ja"), std::string(), contents, &predicted_language,
+      &is_prediction_reliable, model_reliability_score);
+  EXPECT_TRUE(is_prediction_reliable);
+  EXPECT_EQ("zh-CN", predicted_language);
+  EXPECT_EQ(translate::kUnknownLanguageCode, language);
+  histogram_tester.ExpectUniqueSample(
+      "LanguageDetection.TFLite.DidAttemptDetection", true, 1);
+}
+
 }  // namespace translate
\ No newline at end of file
diff --git a/components/viz/BUILD.gn b/components/viz/BUILD.gn
index 49ef7f14..7735378 100644
--- a/components/viz/BUILD.gn
+++ b/components/viz/BUILD.gn
@@ -34,6 +34,15 @@
     # The top level test target must depend on the java library directly.
     deps += [ "//components/viz/service:service_java" ]
   }
+
+  if (is_fuchsia) {
+    additional_manifests = [
+      # TODO(crbug.com/1185811): Figure out why jit_capabilities is needed.
+      "//build/config/fuchsia/test/jit_capabilities.test-cmx",
+
+      "//build/config/fuchsia/test/vulkan_capabilities.test-cmx",
+    ]
+  }
 }
 
 viz_test("viz_perftests") {
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index b7233de6..3512ad73 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -134,9 +134,13 @@
   return base::FeatureList::IsEnabled(kEnableOverlayPrioritization);
 }
 
-bool IsVizHitTestingDebugEnabled() {
-  return base::CommandLine::ForCurrentProcess()->HasSwitch(
-      switches::kEnableVizHitTestDebug);
+// If a synchronous IPC should used when destroying windows. This exists to test
+// the impact of removing the sync IPC.
+bool IsSyncWindowDestructionEnabled() {
+  static constexpr base::Feature kSyncWindowDestruction{
+      "SyncWindowDestruction", base::FEATURE_ENABLED_BY_DEFAULT};
+
+  return base::FeatureList::IsEnabled(kSyncWindowDestruction);
 }
 
 bool IsUsingSkiaRenderer() {
@@ -212,6 +216,11 @@
   return base::FeatureList::IsEnabled(kUsePreferredIntervalForVideo);
 }
 
+bool IsVizHitTestingDebugEnabled() {
+  return base::CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kEnableVizHitTestDebug);
+}
+
 bool ShouldUseRealBuffersForPageFlipTest() {
   return base::FeatureList::IsEnabled(kUseRealBuffersForPageFlipTest);
 }
diff --git a/components/viz/common/features.h b/components/viz/common/features.h
index fd9434a3..5161da69 100644
--- a/components/viz/common/features.h
+++ b/components/viz/common/features.h
@@ -40,16 +40,17 @@
 VIZ_COMMON_EXPORT extern const base::Feature kWebViewVulkanIntermediateBuffer;
 
 VIZ_COMMON_EXPORT bool IsAdpfEnabled();
-VIZ_COMMON_EXPORT bool IsVizHitTestingDebugEnabled();
-VIZ_COMMON_EXPORT bool IsUsingSkiaRenderer();
 #if defined(OS_ANDROID)
 VIZ_COMMON_EXPORT bool IsDynamicColorGamutEnabled();
 #endif
 VIZ_COMMON_EXPORT bool IsOverlayPrioritizationEnabled();
+VIZ_COMMON_EXPORT bool IsSyncWindowDestructionEnabled();
 VIZ_COMMON_EXPORT bool IsUsingFastPathForSolidColorQuad();
+VIZ_COMMON_EXPORT bool IsUsingSkiaRenderer();
 VIZ_COMMON_EXPORT bool IsUsingVizForWebView();
 VIZ_COMMON_EXPORT bool IsUsingVizFrameSubmissionForWebView();
 VIZ_COMMON_EXPORT bool IsUsingPreferredIntervalForVideo();
+VIZ_COMMON_EXPORT bool IsVizHitTestingDebugEnabled();
 VIZ_COMMON_EXPORT bool ShouldUseRealBuffersForPageFlipTest();
 VIZ_COMMON_EXPORT bool ShouldWebRtcLogCapturePipeline();
 #if defined(OS_WIN)
diff --git a/components/viz/host/host_frame_sink_manager.cc b/components/viz/host/host_frame_sink_manager.cc
index c2af78a1..0184721 100644
--- a/components/viz/host/host_frame_sink_manager.cc
+++ b/components/viz/host/host_frame_sink_manager.cc
@@ -13,6 +13,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/stl_util.h"
 #include "base/time/time.h"
+#include "components/viz/common/features.h"
 #include "components/viz/common/surfaces/surface_info.h"
 #include "components/viz/host/renderer_settings_creation.h"
 #include "mojo/public/cpp/bindings/sync_call_restrictions.h"
@@ -21,7 +22,9 @@
 namespace viz {
 
 HostFrameSinkManager::HostFrameSinkManager()
-    : debug_renderer_settings_(CreateDefaultDebugRendererSettings()) {}
+    : enable_sync_window_destruction_(
+          features::IsSyncWindowDestructionEnabled()),
+      debug_renderer_settings_(CreateDefaultDebugRendererSettings()) {}
 
 HostFrameSinkManager::~HostFrameSinkManager() = default;
 
@@ -144,7 +147,8 @@
   data.has_created_compositor_frame_sink = true;
 
   // Only wait on destruction if using GPU compositing for the window.
-  data.wait_on_destruction = params->gpu_compositing;
+  data.wait_on_destruction =
+      enable_sync_window_destruction_ && params->gpu_compositing;
 
   frame_sink_manager_->CreateRootCompositorFrameSink(std::move(params));
   display_hit_test_query_[frame_sink_id] = std::make_unique<HitTestQuery>();
diff --git a/components/viz/host/host_frame_sink_manager.h b/components/viz/host/host_frame_sink_manager.h
index e2a24069..d66498aa 100644
--- a/components/viz/host/host_frame_sink_manager.h
+++ b/components/viz/host/host_frame_sink_manager.h
@@ -269,6 +269,8 @@
       const FrameSinkId& frame_sink_id,
       const std::vector<AggregatedHitTestRegion>& hit_test_data) override;
 
+  const bool enable_sync_window_destruction_;
+
   // This will point to |frame_sink_manager_remote_| if using mojo or it may
   // point directly at FrameSinkManagerImpl in tests. Use this to make function
   // calls.
diff --git a/components/viz/service/display/ca_layer_overlay.cc b/components/viz/service/display/ca_layer_overlay.cc
index 9dc3acd0..171b2e6 100644
--- a/components/viz/service/display/ca_layer_overlay.cc
+++ b/components/viz/service/display/ca_layer_overlay.cc
@@ -302,6 +302,75 @@
 CALayerOverlay& CALayerOverlay::operator=(const CALayerOverlay& other) =
     default;
 
+bool CALayerOverlayProcessor::AreClipSettingsValid(
+    const CALayerOverlay& ca_layer_overlay,
+    CALayerOverlayList* ca_layer_overlay_list) const {
+  // It is not possible to correctly represent two different clipping
+  // settings within one sorting context.
+  if (!ca_layer_overlay_list->empty()) {
+    const CALayerOverlay& previous_ca_layer = ca_layer_overlay_list->back();
+    if (ca_layer_overlay.shared_state->sorting_context_id &&
+        previous_ca_layer.shared_state->sorting_context_id ==
+            ca_layer_overlay.shared_state->sorting_context_id) {
+      if (previous_ca_layer.shared_state->is_clipped !=
+              ca_layer_overlay.shared_state->is_clipped ||
+          previous_ca_layer.shared_state->clip_rect !=
+              ca_layer_overlay.shared_state->clip_rect) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+void CALayerOverlayProcessor::PutHDRContentInSeparateOverlay(
+    DisplayResourceProvider* resource_provider,
+    AggregatedRenderPass* render_pass,
+    const gfx::RectF& display_rect,
+    QuadList* quad_list,
+    const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
+        render_pass_filters,
+    const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
+        render_pass_backdrop_filters,
+    CALayerOverlayList* ca_layer_overlays) const {
+  CALayerResult result = CA_LAYER_SUCCESS;
+  CALayerOverlayProcessorInternal processor;
+
+  for (auto it = quad_list->begin(); it != quad_list->end(); ++it) {
+    const DrawQuad* quad = *it;
+    if (quad->material == ContentDrawQuadBase::Material::kTextureContent) {
+      const TextureDrawQuad* texture_quad = TextureDrawQuad::MaterialCast(quad);
+      if (!resource_provider->GetColorSpace(texture_quad->resource_id())
+               .IsHDR())
+        continue;
+
+      CALayerOverlay ca_layer;
+      bool skip = false;
+      bool render_pass_draw_quad = false;
+      result = processor.FromDrawQuad(resource_provider, display_rect, quad,
+                                      render_pass_filters,
+                                      render_pass_backdrop_filters, &ca_layer,
+                                      &skip, &render_pass_draw_quad);
+      if (result != CA_LAYER_SUCCESS)
+        break;
+
+      if (skip)
+        continue;
+
+      if (!AreClipSettingsValid(ca_layer, ca_layer_overlays)) {
+        result = CA_LAYER_FAILED_DIFFERENT_CLIP_SETTINGS;
+        break;
+      }
+
+      render_pass->ReplaceExistingQuadWithOpaqueTransparentSolidColor(it);
+      ca_layer_overlays->push_back(ca_layer);
+    }
+  }
+  if (result != CA_LAYER_SUCCESS)
+    ca_layer_overlays->clear();
+}
+
 bool CALayerOverlayProcessor::ProcessForCALayerOverlays(
     DisplayResourceProvider* resource_provider,
     const gfx::RectF& display_rect,
@@ -350,21 +419,9 @@
     if (skip)
       continue;
 
-    // It is not possible to correctly represent two different clipping settings
-    // within one sorting context.
-    if (!ca_layer_overlays->empty()) {
-      const CALayerOverlay& previous_ca_layer = ca_layer_overlays->back();
-      if (ca_layer.shared_state->sorting_context_id &&
-          previous_ca_layer.shared_state->sorting_context_id ==
-              ca_layer.shared_state->sorting_context_id) {
-        if (previous_ca_layer.shared_state->is_clipped !=
-                ca_layer.shared_state->is_clipped ||
-            previous_ca_layer.shared_state->clip_rect !=
-                ca_layer.shared_state->clip_rect) {
-          result = CA_LAYER_FAILED_DIFFERENT_CLIP_SETTINGS;
-          break;
-        }
-      }
+    if (!AreClipSettingsValid(ca_layer, ca_layer_overlays)) {
+      result = CA_LAYER_FAILED_DIFFERENT_CLIP_SETTINGS;
+      break;
     }
 
     ca_layer_overlays->push_back(ca_layer);
diff --git a/components/viz/service/display/ca_layer_overlay.h b/components/viz/service/display/ca_layer_overlay.h
index 578b197..2038730 100644
--- a/components/viz/service/display/ca_layer_overlay.h
+++ b/components/viz/service/display/ca_layer_overlay.h
@@ -94,6 +94,19 @@
   CALayerOverlayProcessor() = default;
   virtual ~CALayerOverlayProcessor() = default;
 
+  bool AreClipSettingsValid(const CALayerOverlay& ca_layer_overlay,
+                            CALayerOverlayList* ca_layer_overlay_list) const;
+  void PutHDRContentInSeparateOverlay(
+      DisplayResourceProvider* resource_provider,
+      AggregatedRenderPass* render_pass,
+      const gfx::RectF& display_rect,
+      QuadList* quad_list,
+      const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
+          render_pass_filters,
+      const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
+          render_pass_backdrop_filters,
+      CALayerOverlayList* ca_layer_overlays) const;
+
   // Returns true if all quads in the root render pass have been replaced by
   // CALayerOverlays. Virtual for testing.
   virtual bool ProcessForCALayerOverlays(
diff --git a/components/viz/service/display/overlay_processor_mac.cc b/components/viz/service/display/overlay_processor_mac.cc
index d1816cc..0fb897d 100644
--- a/components/viz/service/display/overlay_processor_mac.cc
+++ b/components/viz/service/display/overlay_processor_mac.cc
@@ -99,9 +99,10 @@
     *damage_rect = gfx::Rect();
   }
 
-  // TODO(https://crbug.com/1152849): If there is any HDR or protected content,
-  // use an underlay strategy to move those to |candidates| and replace them
-  // with transparent quads in |render_pass->quad_list|.
+  ca_layer_overlay_processor_->PutHDRContentInSeparateOverlay(
+      resource_provider, render_pass.get(),
+      gfx::RectF(render_pass->output_rect), &render_pass->quad_list,
+      render_pass_filters, render_pass_backdrop_filters, candidates);
 }
 
 void OverlayProcessorMac::AdjustOutputSurfaceOverlay(
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index f6f0ff1..ec5a035 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -1378,7 +1378,15 @@
 
   // In order to concatenate the bypass'ed quads transform with RP itself, it
   // needs to be invertible.
-  if (!quad->shared_quad_state->quad_to_target_transform.IsInvertible())
+  // TODO(michaelludwig) - See crbug.com/1175981 and crbug.com/1186657;
+  // We can't use gfx::Transform.IsInvertible() since that checks the 4x4 matrix
+  // and the rest of skia_renderer->Skia flattens to a 3x3 matrix, which can
+  // change invertibility.
+  SkMatrix flattened;
+  gfx::TransformToFlattenedSkMatrix(
+      quad->shared_quad_state->quad_to_target_transform,
+      &flattened);
+  if (!flattened.invert(nullptr))
     return nullptr;
 
   // A renderpass normally draws its content into a transparent destination,
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc
index d4f28bb..2959e10 100644
--- a/components/viz/service/display/surface_aggregator.cc
+++ b/components/viz/service/display/surface_aggregator.cc
@@ -240,15 +240,14 @@
 
 gfx::Rect SurfaceAggregator::DamageRectForSurface(
     const Surface* surface,
-    const CompositorRenderPass& source,
-    const gfx::Rect& full_rect) const {
+    const CompositorRenderPass& source) const {
   // If we have damage because of surface animation, return the source damage
   // since we trust the source damage to have correctly computed damage, and we
   // can't skip it.
   // TODO(vmpstr): This damage may be too large, but I think it's hard to figure
   // out a small bounds on the damage given an animation that happens in
   // SurfaceAnimationManager.
-  if (surface->HasSurfaceAnimationDamange())
+  if (surface->HasSurfaceAnimationDamage())
     return source.damage_rect;
 
   if (IsSurfaceFrameIndexSameAsPrevious(surface))
@@ -266,7 +265,7 @@
       return source.damage_rect;
   }
 
-  return full_rect;
+  return source.output_rect;
 }
 
 // This function is called at each render pass - CopyQuadsToPass().
@@ -328,8 +327,7 @@
                                   dest_pass->cache_render_pass)) {
       damage_rect = source_pass->output_rect;
     } else {
-      damage_rect =
-          DamageRectForSurface(surface, *source_pass, source_pass->output_rect);
+      damage_rect = DamageRectForSurface(surface, *source_pass);
     }
   }
 
@@ -364,15 +362,13 @@
     const CompositorRenderPass& source_pass,
     AggregatedRenderPass* dest_pass,
     const gfx::Transform& parent_target_transform,
-    const SurfaceId& surface_id,
+    const Surface* surface,
     const ClipData& clip_rect,
     size_t* overlay_damage_index) {
-  Surface* surface = manager_->GetSurfaceForId(surface_id);
-
   // If we have damage from a surface animation, then we shouldn't have an
   // overlay candidate from the root render pass, since that's an interpolated
   // pass with "artificial" damage.
-  if (surface->HasSurfaceAnimationDamange())
+  if (surface->HasSurfaceAnimationDamage())
     return nullptr;
 
   // Only process the damage rect at the root render pass, once per surface.
@@ -686,7 +682,7 @@
         dest_pass->transform_to_root_target);
 
     CopyQuadsToPass(source, copy_pass.get(), frame.device_scale_factor(),
-                    child_to_parent_map, gfx::Transform(), {}, surface_id,
+                    child_to_parent_map, gfx::Transform(), {}, surface,
                     MaskFilterInfoExt());
 
     // If the render pass has copy requests, or should be cached, or has
@@ -715,13 +711,12 @@
   // damage because HandleSurfaceQuad is a recursive call by calling
   // CopyQuadsToPass in it.
   dest_pass->has_damage_from_contributing_content |=
-      !DamageRectForSurface(surface, last_pass, last_pass.output_rect)
-           .IsEmpty();
+      !DamageRectForSurface(surface, last_pass).IsEmpty();
 
   if (merge_pass) {
     CopyQuadsToPass(last_pass, dest_pass, frame.device_scale_factor(),
                     child_to_parent_map, combined_transform, quads_clip,
-                    surface_id, mask_filter_info);
+                    surface, mask_filter_info);
   } else {
     auto* shared_quad_state = CopyAndScaleSharedQuadState(
         source_sqs, scaled_quad_to_target_transform, target_transform,
@@ -1032,7 +1027,7 @@
         child_to_parent_map,
     const gfx::Transform& target_transform,
     const ClipData& clip_rect,
-    const SurfaceId& surface_id,
+    const Surface* surface,
     const MaskFilterInfoExt& parent_mask_filter_info_ext) {
   const QuadList& source_quad_list = source_pass.quad_list;
   const SharedQuadState* last_copied_source_shared_quad_state = nullptr;
@@ -1075,7 +1070,7 @@
                                           dest_pass);
     quad_with_overlay_damage_index =
         FindQuadWithOverlayDamage(source_pass, dest_pass, target_transform,
-                                  surface_id, clip_rect, &overlay_damage_index);
+                                  surface, clip_rect, &overlay_damage_index);
   }
 
   MaskFilterInfoExt new_mask_filter_info_ext = parent_mask_filter_info_ext;
@@ -1123,7 +1118,7 @@
           // If a surface is being drawn for a second time, clear our
           // |de_jelly_delta_y|, as de-jelly is only needed the first time
           // a surface draws.
-          if (!new_surfaces_.count(surface_id))
+          if (!new_surfaces_.count(surface->surface_id()))
             dest_shared_quad_state->de_jelly_delta_y = 0.0f;
         }
 
@@ -1148,7 +1143,7 @@
             CompositorRenderPassDrawQuad::MaterialCast(quad);
         CompositorRenderPassId original_pass_id = pass_quad->render_pass_id;
         AggregatedRenderPassId remapped_pass_id =
-            pass_id_remapper_.Remap(original_pass_id, surface_id);
+            pass_id_remapper_.Remap(original_pass_id, surface->surface_id());
 
         // If the CompositorRenderPassDrawQuad is referring to other render pass
         // with the |has_damage_from_contributing_content| set on it, then the
@@ -1278,7 +1273,7 @@
                     child_to_parent_map,
                     apply_surface_transform_to_root_pass ? surface_transform
                                                          : gfx::Transform(),
-                    {}, surface->surface_id(), MaskFilterInfoExt());
+                    {}, surface, MaskFilterInfoExt());
 
     // If the render pass has copy requests, or should be cached, or has
     // moving-pixel filters, or in a moving-pixel surface, we should damage the
@@ -1346,14 +1341,12 @@
 
   const CompositorFrame& frame = surface->GetActiveOrInterpolatedFrame();
   CompositorRenderPass* last_pass = frame.render_pass_list.back().get();
-  gfx::Rect full_damage = last_pass->output_rect;
 
   // The damage on the root render pass of the surface comes from damage
   // accumulated from all quads in the surface, and needs to be expanded by any
   // pixel-moving backdrop filter in the render pass if intersecting. Transform
   // this damage into the local space of the render pass for this purpose.
-  gfx::Rect surface_root_rp_damage =
-      DamageRectForSurface(surface, *last_pass, full_damage);
+  gfx::Rect surface_root_rp_damage = DamageRectForSurface(surface, *last_pass);
   if (!surface_root_rp_damage.IsEmpty()) {
     gfx::Transform root_to_target_transform(
         gfx::Transform::kSkipInitialization);
@@ -1432,9 +1425,6 @@
         child_rect = gfx::ScaleToEnclosingRect(child_rect, x_scale, y_scale);
         quad_damage_rect.Union(child_rect);
       }
-
-      if (quad_damage_rect.IsEmpty())
-        continue;
     } else if (quad->material == DrawQuad::Material::kCompositorRenderPass) {
       auto* render_pass_quad = CompositorRenderPassDrawQuad::MaterialCast(quad);
 
@@ -1523,22 +1513,22 @@
       quad_damage_rect = PrewalkRenderPass(
           &child_render_pass_entry, surface, render_pass_map, will_draw,
           gfx::Rect(), child_to_root_transform, in_moved_pixel_rp, result);
+    }
 
-    } else {
-      continue;
+    if (!quad_damage_rect.IsEmpty()) {
+      // Convert the quad damage rect into its target space and clip it if
+      // needed. Ignore tiny errors to avoid artificially inflating the
+      // damage due to floating point math.
+      constexpr float kEpsilon = 0.001f;
+      gfx::Rect rect_in_target_space =
+          cc::MathUtil::MapEnclosingClippedRectIgnoringError(
+              quad->shared_quad_state->quad_to_target_transform,
+              quad_damage_rect, kEpsilon);
+      if (quad->shared_quad_state->is_clipped) {
+        rect_in_target_space.Intersect(quad->shared_quad_state->clip_rect);
+      }
+      damage_rect.Union(rect_in_target_space);
     }
-    // Convert the quad damage rect into its target space and clip it if
-    // needed. Ignore tiny errors to avoid artificially inflating the
-    // damage due to floating point math.
-    constexpr float kEpsilon = 0.001f;
-    gfx::Rect rect_in_target_space =
-        cc::MathUtil::MapEnclosingClippedRectIgnoringError(
-            quad->shared_quad_state->quad_to_target_transform, quad_damage_rect,
-            kEpsilon);
-    if (quad->shared_quad_state->is_clipped) {
-      rect_in_target_space.Intersect(quad->shared_quad_state->clip_rect);
-    }
-    damage_rect.Union(rect_in_target_space);
   }
 
   // Expand the damage to cover entire |output_rect| if the |render_pass| has
@@ -1592,6 +1582,17 @@
   return true;
 }
 
+bool SurfaceAggregator::CheckFrameSinksChanged(const Surface* surface) {
+  contained_surfaces_[surface->surface_id()] = surface->GetActiveFrameIndex();
+  LocalSurfaceId& local_surface_id =
+      contained_frame_sinks_[surface->surface_id().frame_sink_id()];
+  bool frame_sinks_changed = (!previous_contained_frame_sinks_.contains(
+      surface->surface_id().frame_sink_id()));
+  local_surface_id =
+      std::max(surface->surface_id().local_surface_id(), local_surface_id);
+  return frame_sinks_changed;
+}
+
 gfx::Rect SurfaceAggregator::PrewalkSurface(
     Surface* surface,
     bool in_moved_pixel_rp,
@@ -1602,13 +1603,7 @@
   if (referenced_surfaces_.count(surface->surface_id()))
     return gfx::Rect();
 
-  contained_surfaces_[surface->surface_id()] = surface->GetActiveFrameIndex();
-  LocalSurfaceId& local_surface_id =
-      contained_frame_sinks_[surface->surface_id().frame_sink_id()];
-  result->frame_sinks_changed |= (!previous_contained_frame_sinks_.contains(
-      surface->surface_id().frame_sink_id()));
-  local_surface_id =
-      std::max(surface->surface_id().local_surface_id(), local_surface_id);
+  result->frame_sinks_changed |= CheckFrameSinksChanged(surface);
 
   if (!surface->HasActiveFrame())
     return gfx::Rect();
@@ -1619,9 +1614,6 @@
   if (parent_pass_id)
     render_pass_dependencies_[parent_pass_id].insert(remapped_pass_id);
 
-  const gfx::Transform& root_pass_transform =
-      IsRootSurface(surface) ? root_surface_transform_ : gfx::Transform();
-
   base::flat_map<CompositorRenderPassId, RenderPassMapEntry> render_pass_map =
       GenerateRenderPassMap(frame.render_pass_list, IsRootSurface(surface));
 
@@ -1632,9 +1624,7 @@
   valid_surfaces_.insert(surface->surface_id());
 
   CompositorRenderPass* last_pass = frame.render_pass_list.back().get();
-  gfx::Rect full_damage = last_pass->output_rect;
-  gfx::Rect damage_rect =
-      DamageRectForSurface(surface, *last_pass, full_damage);
+  gfx::Rect damage_rect = DamageRectForSurface(surface, *last_pass);
 
   // Avoid infinite recursion by adding current surface to
   // |referenced_surfaces_|.
@@ -1647,23 +1637,28 @@
   damage_rect.Union(PrewalkRenderPass(
       &entry, surface, &render_pass_map, will_draw, damage_from_parent,
       gfx::Transform(), in_moved_pixel_rp, result));
-  damage_rect = cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(
-      root_pass_transform, damage_rect);
 
   if (!damage_rect.IsEmpty()) {
+    auto damage_rect_surface_space = damage_rect;
+    if (IsRootSurface(surface)) {
+      // The damage reported to the surface is in pre-display transform space
+      // since it is used by clients which are not aware of the display
+      // transform.
+      damage_rect = cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(
+          root_surface_transform_, damage_rect);
+      gfx::Transform inverse(gfx::Transform::kSkipInitialization);
+      bool inverted = root_surface_transform_.GetInverse(&inverse);
+      DCHECK(inverted);
+      damage_rect_surface_space =
+          cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(inverse,
+                                                                  damage_rect);
+    }
+
     // The following call can cause one or more copy requests to be added to the
     // Surface. Therefore, no code before this point should have assumed
     // anything about the presence or absence of copy requests after this point.
-
-    // The damage reported to the surface is in pre-display transform space
-    // since it is used by clients which are not aware of the display transform.
-    gfx::Transform inverse(gfx::Transform::kSkipInitialization);
-    bool inverted = root_pass_transform.GetInverse(&inverse);
-    DCHECK(inverted);
-    surface->NotifyAggregatedDamage(
-        cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(inverse,
-                                                                damage_rect),
-        expected_display_time_);
+    surface->NotifyAggregatedDamage(damage_rect_surface_space,
+                                    expected_display_time_);
   }
 
   // If any CopyOutputRequests were made at FrameSink level, make sure we grab
@@ -1807,12 +1802,8 @@
   Surface* surface = manager_->GetSurfaceForId(surface_id);
   DCHECK(surface);
   DCHECK(contained_surfaces_.empty());
-  contained_surfaces_[surface_id] = surface->GetActiveFrameIndex();
 
-  LocalSurfaceId& local_surface_id =
-      contained_frame_sinks_[surface_id.frame_sink_id()];
-  local_surface_id =
-      std::max(surface->surface_id().local_surface_id(), local_surface_id);
+  CheckFrameSinksChanged(surface);
 
   if (!surface->HasActiveFrame())
     return {};
@@ -1864,10 +1855,9 @@
     root_damage_rect_ = cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(
         root_surface_transform_,
         gfx::Rect(root_surface_frame.size_in_pixels()));
+    root_content_color_usage_ = prewalk_result.content_color_usage;
   }
 
-  root_content_color_usage_ = prewalk_result.content_color_usage;
-
   if (prewalk_result.frame_sinks_changed)
     manager_->AggregatedFrameSinksChanged();
 
diff --git a/components/viz/service/display/surface_aggregator.h b/components/viz/service/display/surface_aggregator.h
index 4906d71d..40e8427 100644
--- a/components/viz/service/display/surface_aggregator.h
+++ b/components/viz/service/display/surface_aggregator.h
@@ -170,7 +170,7 @@
           resource_to_child_map,
       const gfx::Transform& target_transform,
       const ClipData& clip_rect,
-      const SurfaceId& surface_id,
+      const Surface* surface,
       const MaskFilterInfoExt& mask_filter_info_pair);
 
   // Recursively walks through the render pass and updates the
@@ -245,6 +245,8 @@
 
   void PropagateCopyRequestPasses();
 
+  bool CheckFrameSinksChanged(const Surface* surface);
+
   // Returns true if the quad list from the render pass provided can be merged
   // with its target render pass based on mask filter info.
   bool CanMergeMaskFilterInfo(const MaskFilterInfoExt& mask_filter_info_pair,
@@ -253,8 +255,7 @@
   int ChildIdForSurface(Surface* surface);
   bool IsSurfaceFrameIndexSameAsPrevious(const Surface* surface) const;
   gfx::Rect DamageRectForSurface(const Surface* surface,
-                                 const CompositorRenderPass& source,
-                                 const gfx::Rect& full_rect) const;
+                                 const CompositorRenderPass& source) const;
 
   // This function adds |damage_rect| to
   // |damage_rects_union_of_surfaces_on_top_|. |damage_rect| is in the quad
@@ -279,7 +280,7 @@
       const CompositorRenderPass& source_pass,
       AggregatedRenderPass* dest_pass,
       const gfx::Transform& parent_target_transform,
-      const SurfaceId& surface_id,
+      const Surface* surface,
       const ClipData& clip_rect,
       size_t* overlay_damage_index);
 
diff --git a/components/viz/service/surfaces/surface.cc b/components/viz/service/surfaces/surface.cc
index cb4a9613..d6d18879 100644
--- a/components/viz/service/surfaces/surface.cc
+++ b/components/viz/service/surfaces/surface.cc
@@ -635,7 +635,7 @@
   interpolated_frame_.emplace(std::move(frame));
 }
 
-bool Surface::HasSurfaceAnimationDamange() const {
+bool Surface::HasSurfaceAnimationDamage() const {
   return interpolated_frame_.has_value() || has_damage_from_interpolated_frame_;
 }
 
diff --git a/components/viz/service/surfaces/surface.h b/components/viz/service/surfaces/surface.h
index 42ad6263..101b595 100644
--- a/components/viz/service/surfaces/surface.h
+++ b/components/viz/service/surfaces/surface.h
@@ -173,7 +173,7 @@
   // Returns true if the active or interpolated frame has damage due to a
   // surface animation. This means that the damage should be respected even if
   // the active frame index has not changed.
-  bool HasSurfaceAnimationDamange() const;
+  bool HasSurfaceAnimationDamage() const;
 
   // Returns the currently pending frame. You must check where HasPendingFrame()
   // returns true before calling this method.
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index 7c9df76..fa874629 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -1309,9 +1309,6 @@
     case ax::mojom::Role::kIgnored:
       // No role description.
       break;
-    case ax::mojom::Role::kImageMap:
-      message_id = IDS_AX_ROLE_GRAPHIC;
-      break;
     case ax::mojom::Role::kImage:
       message_id = IDS_AX_ROLE_GRAPHIC;
       break;
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm
index fed245cf..5885c36 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa.mm
+++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -1995,6 +1995,9 @@
               _owner->HasState(ax::mojom::State::kMultiline)) ||
              (_owner->IsRichTextField() && !ui::IsComboBox(role))) {
     cocoa_role = NSAccessibilityTextAreaRole;
+  } else if (role == ax::mojom::Role::kImage && _owner->GetChildCount()) {
+    // An image map is an image with children, and exposed on Mac as a group.
+    cocoa_role = NSAccessibilityGroupRole;
   } else if (role == ax::mojom::Role::kImage &&
              _owner->HasExplicitlyEmptyName()) {
     cocoa_role = NSAccessibilityUnknownRole;
diff --git a/content/browser/accessibility/dump_accessibility_events_browsertest.cc b/content/browser/accessibility/dump_accessibility_events_browsertest.cc
index dc89ab0..60ad40a 100644
--- a/content/browser/accessibility/dump_accessibility_events_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_events_browsertest.cc
@@ -476,6 +476,11 @@
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
+                       AccessibilityEventsAddAlertWithRoleChange) {
+  RunEventTest(FILE_PATH_LITERAL("add-alert-with-role-change.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
                        AccessibilityEventsAddChild) {
   RunEventTest(FILE_PATH_LITERAL("add-child.html"));
 }
diff --git a/content/browser/background_sync/background_sync_launcher_unittest.cc b/content/browser/background_sync/background_sync_launcher_unittest.cc
index cd28e32..f0a80c8 100644
--- a/content/browser/background_sync/background_sync_launcher_unittest.cc
+++ b/content/browser/background_sync/background_sync_launcher_unittest.cc
@@ -44,7 +44,7 @@
     DCHECK(browser_context);
     auto partition_num = std::to_string(++partition_count_);
     return StoragePartitionConfig::Create(
-        std::string("PartitionDomain") + partition_num,
+        browser_context, std::string("PartitionDomain") + partition_num,
         std::string("Partition") + partition_num, false /* in_memory */);
   }
 
diff --git a/content/browser/background_sync/background_sync_scheduler_unittest.cc b/content/browser/background_sync/background_sync_scheduler_unittest.cc
index 46c3a22..6d15fe7 100644
--- a/content/browser/background_sync/background_sync_scheduler_unittest.cc
+++ b/content/browser/background_sync/background_sync_scheduler_unittest.cc
@@ -42,8 +42,8 @@
       BrowserContext* browser_context,
       const GURL& site) override {
     return content::StoragePartitionConfig::Create(
-        "PartitionDomain" + site.spec(), "Partition" + site.spec(),
-        false /* in_memory */);
+        browser_context, "PartitionDomain" + site.spec(),
+        "Partition" + site.spec(), false /* in_memory */);
   }
 };
 
diff --git a/content/browser/browser_context.cc b/content/browser/browser_context.cc
index 73c482c..f03128b 100644
--- a/content/browser/browser_context.cc
+++ b/content/browser/browser_context.cc
@@ -221,7 +221,8 @@
     bool can_create) {
   if (!site_instance) {
     return GetStoragePartition(
-        browser_context, StoragePartitionConfig::CreateDefault(), can_create);
+        browser_context, StoragePartitionConfig::CreateDefault(browser_context),
+        can_create);
   }
 
   return GetStoragePartitionForSite(browser_context,
@@ -235,11 +236,12 @@
   StoragePartitionImplMap* partition_map =
       GetStoragePartitionMap(browser_context);
 
-  auto config_to_use = storage_partition_config;
-  if (browser_context->IsOffTheRecord())
-    config_to_use = storage_partition_config.CopyWithInMemorySet();
+  if (browser_context->IsOffTheRecord()) {
+    // An off the record profile MUST only use in memory storage partitions.
+    CHECK(storage_partition_config.in_memory());
+  }
 
-  return partition_map->Get(config_to_use, can_create);
+  return partition_map->Get(storage_partition_config, can_create);
 }
 
 StoragePartition* BrowserContext::GetStoragePartitionForSite(
@@ -276,8 +278,8 @@
 
 StoragePartition* BrowserContext::GetDefaultStoragePartition(
     BrowserContext* browser_context) {
-  return GetStoragePartition(browser_context,
-                             StoragePartitionConfig::CreateDefault());
+  return GetStoragePartition(
+      browser_context, StoragePartitionConfig::CreateDefault(browser_context));
 }
 
 // static
diff --git a/content/browser/conversions/conversion_host.cc b/content/browser/conversions/conversion_host.cc
index 843630e..d5765eb 100644
--- a/content/browser/conversions/conversion_host.cc
+++ b/content/browser/conversions/conversion_host.cc
@@ -85,7 +85,7 @@
 }
 
 void ConversionHost::DidStartNavigation(NavigationHandle* navigation_handle) {
-  // Navigations with an impression set should only occur in the main frame.
+  // Impression navigations need to navigate the main frame to be valid.
   if (!navigation_handle->GetImpression() ||
       !navigation_handle->IsInMainFrame() ||
       !conversion_manager_provider_->GetManager(web_contents())) {
diff --git a/content/browser/conversions/impression_declaration_browsertest.cc b/content/browser/conversions/impression_declaration_browsertest.cc
index 302e2a9..391eb8b3 100644
--- a/content/browser/conversions/impression_declaration_browsertest.cc
+++ b/content/browser/conversions/impression_declaration_browsertest.cc
@@ -284,6 +284,37 @@
   EXPECT_EQ(1UL, last_impression.impression_data);
 }
 
+// See https://crbug.com/1186077.
+IN_PROC_BROWSER_TEST_F(
+    ImpressionDeclarationBrowserTest,
+    TagNavigatesFromMiddleClickInSubframe_ImpressionReceived) {
+  GURL page_url = https_server()->GetURL("b.test", "/page_with_iframe.html");
+  EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
+  EXPECT_TRUE(ExecJs(shell(), R"(
+     let frame = document.getElementById('test_iframe');
+     frame.setAttribute('allow', 'conversion-measurement');)"));
+
+  GURL subframe_url =
+      https_server()->GetURL("c.test", "/page_with_impression_creator.html");
+  NavigateIframeToURL(web_contents(), "test_iframe", subframe_url);
+
+  // Create an impression tag that is opened via middle click in the subframe.
+  RenderFrameHost* subframe = ChildFrameAt(web_contents()->GetMainFrame(), 0);
+  EXPECT_TRUE(ExecJs(subframe, R"(
+    createImpressionTag("link",
+                        "page_with_conversion_redirect.html",
+                        "1" /* impression data */,
+                        "https://a.com" /* conversion_destination */);)"));
+
+  ImpressionObserver impression_observer(nullptr);
+  impression_observer.StartWatchingNewWebContents();
+  EXPECT_TRUE(ExecJs(subframe, "simulateMiddleClick(\'link\');"));
+
+  // Verify the navigation was annotated with an impression.
+  blink::Impression last_impression = impression_observer.Wait();
+  EXPECT_EQ(1UL, last_impression.impression_data);
+}
+
 IN_PROC_BROWSER_TEST_F(
     ImpressionDeclarationBrowserTest,
     ImpressionTagNavigatesFromEnterPress_ImpressionReceived) {
diff --git a/content/browser/devtools/protocol/service_worker_handler.cc b/content/browser/devtools/protocol/service_worker_handler.cc
index 7dd623be..9cacf78 100644
--- a/content/browser/devtools/protocol/service_worker_handler.cc
+++ b/content/browser/devtools/protocol/service_worker_handler.cc
@@ -349,10 +349,9 @@
   base::Optional<std::string> payload;
   if (data.size() > 0)
     payload = data;
-  BrowserContext::DeliverPushMessage(
-      browser_context_, GURL(origin), id, /* push_message_id= */ std::string(),
-      std::move(payload),
-      base::BindOnce([](blink::mojom::PushEventStatus status) {}));
+  BrowserContext::DeliverPushMessage(browser_context_, GURL(origin), id,
+                                     /* push_message_id= */ std::string(),
+                                     std::move(payload), base::DoNothing());
 
   return Response::Success();
 }
diff --git a/content/browser/devtools/protocol/target_handler.cc b/content/browser/devtools/protocol/target_handler.cc
index 77defde..8de7e54 100644
--- a/content/browser/devtools/protocol/target_handler.cc
+++ b/content/browser/devtools/protocol/target_handler.cc
@@ -607,9 +607,7 @@
     for (auto* context : delegate->GetBrowserContexts()) {
       if (!dispose_on_detach_context_ids_.contains(context->UniqueId()))
         continue;
-      delegate->DisposeBrowserContext(
-          context,
-          base::BindOnce([](bool success, const std::string& error) {}));
+      delegate->DisposeBrowserContext(context, base::DoNothing());
     }
     dispose_on_detach_context_ids_.clear();
   }
diff --git a/content/browser/gpu/compositor_util.cc b/content/browser/gpu/compositor_util.cc
index 3aaa517c..cc59a49e 100644
--- a/content/browser/gpu/compositor_util.cc
+++ b/content/browser/gpu/compositor_util.cc
@@ -127,11 +127,11 @@
     {"video_decode",
      SafeGetFeatureStatus(gpu_feature_info,
                           gpu::GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE),
-#if defined(OS_LINUX) && !defined(OS_ANDROID)
+#if defined(OS_LINUX)
      !base::FeatureList::IsEnabled(media::kVaapiVideoDecodeLinux),
 #else
      command_line.HasSwitch(switches::kDisableAcceleratedVideoDecode),
-#endif  // defined(OS_LINUX) && !defined(OS_ANDROID)
+#endif  // defined(OS_LINUX)
      DisableInfo::Problem(
          "Accelerated video decode has been disabled, either via blocklist, "
          "about:flags or the command line."),
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc
index 80ab092..25a5780 100644
--- a/content/browser/gpu/gpu_process_host.cc
+++ b/content/browser/gpu/gpu_process_host.cc
@@ -582,10 +582,10 @@
 
   // TODO(sievers): Revisit this behavior. It's not really a crash, but we also
   // want the fallback-to-sw behavior if we cannot initialize the GPU.
+  LOG(ERROR) << "GPU process failed to initialize.";
   host->RecordProcessCrash();
 
   delete host;
-  DLOG(ERROR) << "GpuProcessHost::Init() failed";
   return nullptr;
 }
 
@@ -980,11 +980,13 @@
   if (kind_ == GPU_PROCESS_KIND_SANDBOXED)
     RecordAppContainerStatus(error_code, crashed_before_);
 #endif  // defined(OS_WIN)
+  LOG(ERROR) << "GPU process launch failed: error_code=" << error_code;
   RecordProcessCrash();
 }
 
 void GpuProcessHost::OnProcessCrashed(int exit_code) {
   // Record crash before doing anything that could start a new GPU process.
+  LOG(ERROR) << "GPU process exited unexpectedly: exit_code=" << exit_code;
   RecordProcessCrash();
 
   gpu_host_->OnProcessCrashed();
diff --git a/content/browser/loader/navigation_url_loader_impl.h b/content/browser/loader/navigation_url_loader_impl.h
index fd7b207..1dbaab2 100644
--- a/content/browser/loader/navigation_url_loader_impl.h
+++ b/content/browser/loader/navigation_url_loader_impl.h
@@ -23,6 +23,7 @@
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "third_party/blink/public/common/loader/previews_state.h"
+#include "third_party/blink/public/common/navigation/navigation_policy.h"
 
 namespace net {
 struct RedirectInfo;
diff --git a/content/browser/loader/navigation_url_loader_impl_unittest.cc b/content/browser/loader/navigation_url_loader_impl_unittest.cc
index 8589765..0aeea62 100644
--- a/content/browser/loader/navigation_url_loader_impl_unittest.cc
+++ b/content/browser/loader/navigation_url_loader_impl_unittest.cc
@@ -93,7 +93,6 @@
     params.is_corb_enabled = false;
     url_loader_ = std::make_unique<network::URLLoader>(
         context_.get(), /*url_loader_factory=*/nullptr,
-        nullptr /* network_service_client */,
         nullptr /* network_context_client */,
         base::BindOnce(&TestNavigationLoaderInterceptor::DeleteURLLoader,
                        base::Unretained(this)),
@@ -103,9 +102,9 @@
         0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client_,
         nullptr /* keepalive_statistics_recorder */,
-        nullptr /* network_usage_accumulator */, nullptr /* header_client */,
-        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
-        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
+        nullptr /* header_client */, nullptr /* origin_policy_manager */,
+        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
+        mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         /*devtools_observer=*/mojo::NullRemote());
   }
diff --git a/content/browser/net/accept_header_browsertest.cc b/content/browser/net/accept_header_browsertest.cc
index 3bb1c9a..555d620 100644
--- a/content/browser/net/accept_header_browsertest.cc
+++ b/content/browser/net/accept_header_browsertest.cc
@@ -173,8 +173,7 @@
   // Ensure that if an Accept header is already set, it is not overwritten.
   EXPECT_EQ("custom/type", GetFor("/xhr_with_accept_header"));
 
-  shell()->web_contents()->GetManifest(
-      base::BindOnce([](const GURL&, const blink::Manifest&) {}));
+  shell()->web_contents()->GetManifest(base::DoNothing());
 
   // ResourceType::kSubResource
   EXPECT_EQ("*/*", GetFor("/manifest"));
diff --git a/content/browser/network_service_client.cc b/content/browser/network_service_client.cc
index 75d775d..4147313 100644
--- a/content/browser/network_service_client.cc
+++ b/content/browser/network_service_client.cc
@@ -27,6 +27,7 @@
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/network_service_util.h"
+#include "services/network/public/cpp/network_switches.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "third_party/blink/public/mojom/web_feature/web_feature.mojom.h"
 
@@ -40,13 +41,9 @@
 
 namespace content {
 
-NetworkServiceClient::NetworkServiceClient(
-    mojo::PendingReceiver<network::mojom::NetworkServiceClient>
-        network_service_client_receiver)
-    : receiver_(this, std::move(network_service_client_receiver))
+NetworkServiceClient::NetworkServiceClient()
 #if defined(OS_ANDROID)
-      ,
-      app_status_listener_(base::android::ApplicationStatusListener::New(
+    : app_status_listener_(base::android::ApplicationStatusListener::New(
           base::BindRepeating(&NetworkServiceClient::OnApplicationStateChange,
                               base::Unretained(this))))
 #endif
@@ -157,12 +154,79 @@
 }
 #endif
 
+mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver>
+NetworkServiceClient::BindURLLoaderNetworkServiceObserver() {
+  mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver> remote;
+  url_loader_network_service_observers_.Add(
+      this, remote.InitWithNewPipeAndPassReceiver());
+  return remote;
+}
+
+void NetworkServiceClient::OnSSLCertificateError(
+    const GURL& url,
+    int net_error,
+    const net::SSLInfo& ssl_info,
+    bool fatal,
+    OnSSLCertificateErrorCallback response) {
+  std::move(response).Run(net::ERR_INSECURE_RESPONSE);
+}
+
+void NetworkServiceClient::OnCertificateRequested(
+    const base::Optional<base::UnguessableToken>& window_id,
+    const scoped_refptr<net::SSLCertRequestInfo>& cert_info,
+    mojo::PendingRemote<network::mojom::ClientCertificateResponder>
+        cert_responder_remote) {
+  mojo::Remote<network::mojom::ClientCertificateResponder> cert_responder(
+      std::move(cert_responder_remote));
+
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          network::switches::kIgnoreUrlFetcherCertRequests)) {
+    cert_responder->ContinueWithoutCertificate();
+    return;
+  }
+  cert_responder->CancelRequest();
+}
+
+void NetworkServiceClient::OnAuthRequired(
+    const base::Optional<base::UnguessableToken>& window_id,
+    uint32_t request_id,
+    const GURL& url,
+    bool first_auth_attempt,
+    const net::AuthChallengeInfo& auth_info,
+    const scoped_refptr<net::HttpResponseHeaders>& head_headers,
+    mojo::PendingRemote<network::mojom::AuthChallengeResponder>
+        auth_challenge_responder) {
+  mojo::Remote<network::mojom::AuthChallengeResponder>
+      auth_challenge_responder_remote(std::move(auth_challenge_responder));
+  auth_challenge_responder_remote->OnAuthCredentials(base::nullopt);
+}
+
+void NetworkServiceClient::OnClearSiteData(const GURL& url,
+                                           const std::string& header_value,
+                                           int load_flags,
+                                           OnClearSiteDataCallback callback) {
+  std::move(callback).Run();
+}
+
+void NetworkServiceClient::OnLoadingStateUpdate(
+    network::mojom::LoadInfoPtr info,
+    OnLoadingStateUpdateCallback callback) {
+  std::move(callback).Run();
+}
+
 void NetworkServiceClient::OnDataUseUpdate(
     int32_t network_traffic_annotation_id_hash,
     int64_t recv_bytes,
     int64_t sent_bytes) {
   GetContentClient()->browser()->OnNetworkServiceDataUseUpdate(
+      network::mojom::kBrowserProcessId, MSG_ROUTING_NONE,
       network_traffic_annotation_id_hash, recv_bytes, sent_bytes);
 }
 
+void NetworkServiceClient::Clone(
+    mojo::PendingReceiver<network::mojom::URLLoaderNetworkServiceObserver>
+        observer) {
+  url_loader_network_service_observers_.Add(this, std::move(observer));
+}
+
 }  // namespace content
diff --git a/content/browser/network_service_client.h b/content/browser/network_service_client.h
index c06506ca..2c3ceaf 100644
--- a/content/browser/network_service_client.h
+++ b/content/browser/network_service_client.h
@@ -16,6 +16,7 @@
 #include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/cert/cert_database.h"
 #include "services/network/public/mojom/network_service.mojom.h"
@@ -30,7 +31,7 @@
 class WebRtcConnectionsObserver;
 
 class CONTENT_EXPORT NetworkServiceClient
-    : public network::mojom::NetworkServiceClient,
+    : public network::mojom::URLLoaderNetworkServiceObserver,
 #if defined(OS_ANDROID)
       public net::NetworkChangeNotifier::ConnectionTypeObserver,
       public net::NetworkChangeNotifier::MaxBandwidthObserver,
@@ -39,15 +40,11 @@
 #endif
       public net::CertDatabase::Observer {
  public:
-  explicit NetworkServiceClient(
-      mojo::PendingReceiver<network::mojom::NetworkServiceClient>
-          network_service_client_receiver);
+  NetworkServiceClient();
   ~NetworkServiceClient() override;
 
-  // network::mojom::NetworkServiceClient implementation:
-  void OnDataUseUpdate(int32_t network_traffic_annotation_id_hash,
-                       int64_t recv_bytes,
-                       int64_t sent_bytes) override;
+  mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver>
+  BindURLLoaderNetworkServiceObserver();
 
   // net::CertDatabase::Observer implementation:
   void OnCertDBChanged() override;
@@ -79,7 +76,38 @@
 #endif
 
  private:
-  mojo::Receiver<network::mojom::NetworkServiceClient> receiver_;
+  // network::mojom::URLLoaderNetworkServiceObserver overrides.
+  void OnSSLCertificateError(const GURL& url,
+                             int net_error,
+                             const net::SSLInfo& ssl_info,
+                             bool fatal,
+                             OnSSLCertificateErrorCallback response) override;
+  void OnCertificateRequested(
+      const base::Optional<base::UnguessableToken>& window_id,
+      const scoped_refptr<net::SSLCertRequestInfo>& cert_info,
+      mojo::PendingRemote<network::mojom::ClientCertificateResponder>
+          cert_responder) override;
+  void OnAuthRequired(
+      const base::Optional<base::UnguessableToken>& window_id,
+      uint32_t request_id,
+      const GURL& url,
+      bool first_auth_attempt,
+      const net::AuthChallengeInfo& auth_info,
+      const scoped_refptr<net::HttpResponseHeaders>& head_headers,
+      mojo::PendingRemote<network::mojom::AuthChallengeResponder>
+          auth_challenge_responder) override;
+  void OnClearSiteData(const GURL& url,
+                       const std::string& header_value,
+                       int load_flags,
+                       OnClearSiteDataCallback callback) override;
+  void OnLoadingStateUpdate(network::mojom::LoadInfoPtr info,
+                            OnLoadingStateUpdateCallback callback) override;
+  void OnDataUseUpdate(int32_t network_traffic_annotation_id_hash,
+                       int64_t recv_bytes,
+                       int64_t sent_bytes) override;
+  void Clone(
+      mojo::PendingReceiver<network::mojom::URLLoaderNetworkServiceObserver>
+          listener) override;
 
   std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
 
@@ -91,6 +119,9 @@
   mojo::Remote<network::mojom::NetworkChangeManager> network_change_manager_;
 #endif
 
+  mojo::ReceiverSet<network::mojom::URLLoaderNetworkServiceObserver>
+      url_loader_network_service_observers_;
+
   DISALLOW_COPY_AND_ASSIGN(NetworkServiceClient);
 };
 
diff --git a/content/browser/network_service_instance_impl.cc b/content/browser/network_service_instance_impl.cc
index d525320..3827f4f 100644
--- a/content/browser/network_service_instance_impl.cc
+++ b/content/browser/network_service_instance_impl.cc
@@ -110,12 +110,15 @@
 // |ShutDownNetworkService()|.
 network::NetworkService* g_in_process_instance = nullptr;
 
+static NetworkServiceClient* g_client = nullptr;
+
 void CreateInProcessNetworkServiceOnThread(
     mojo::PendingReceiver<network::mojom::NetworkService> receiver) {
   // The test interface doesn't need to be implemented in the in-process case.
   auto registry = std::make_unique<service_manager::BinderRegistry>();
-  registry->AddInterface(base::BindRepeating(
-      [](mojo::PendingReceiver<network::mojom::NetworkServiceTest>) {}));
+  registry->AddInterface(
+      base::DoNothing::Repeatedly<
+          mojo::PendingReceiver<network::mojom::NetworkServiceTest>>());
   g_in_process_instance = new network::NetworkService(
       std::move(registry), std::move(receiver),
       true /* delay_initialization_until_set_client */);
@@ -153,7 +156,8 @@
   network_service_params->initial_connection_subtype =
       network::mojom::ConnectionSubtype(
           net::NetworkChangeNotifier::GetConnectionSubtype());
-
+  network_service_params->default_observer =
+      g_client->BindURLLoaderNetworkServiceObserver();
 #if defined(OS_POSIX)
   // Send Kerberos environment variables to the network service.
   if (IsOutOfProcessNetworkService()) {
@@ -278,8 +282,6 @@
 }
 #endif  // defined(OS_WIN)
 
-static NetworkServiceClient* g_client = nullptr;
-
 }  // namespace
 
 class NetworkServiceInstancePrivate {
@@ -351,12 +353,12 @@
         }
       }
 
-      mojo::PendingRemote<network::mojom::NetworkServiceClient> client_remote;
-      auto client_receiver = client_remote.InitWithNewPipeAndPassReceiver();
+      delete g_client;  // In case we're recreating the network service.
+      g_client = new NetworkServiceClient();
+
       // Call SetClient before creating NetworkServiceClient, as the latter
       // might make requests to NetworkService that depend on initialization.
-      (*g_network_service_remote)
-          ->SetClient(std::move(client_remote), CreateNetworkServiceParams());
+      (*g_network_service_remote)->SetParams(CreateNetworkServiceParams());
       g_network_service_is_responding = false;
       g_network_service_remote->QueryVersion(base::BindOnce(
           [](base::Time start_time, uint32_t) {
@@ -374,9 +376,6 @@
           },
           base::Time::Now()));
 
-      delete g_client;  // In case we're recreating the network service.
-      g_client = new NetworkServiceClient(std::move(client_receiver));
-
       const base::CommandLine* command_line =
           base::CommandLine::ForCurrentProcess();
       if (command_line->HasSwitch(network::switches::kLogNetLog)) {
diff --git a/content/browser/network_service_restart_browsertest.cc b/content/browser/network_service_restart_browsertest.cc
index 3c29bde..048c46d 100644
--- a/content/browser/network_service_restart_browsertest.cc
+++ b/content/browser/network_service_restart_browsertest.cc
@@ -95,41 +95,6 @@
   return simple_loader->NetError();
 }
 
-std::vector<network::mojom::NetworkUsagePtr> GetTotalNetworkUsages() {
-  std::vector<network::mojom::NetworkUsagePtr> network_usages;
-  base::RunLoop run_loop;
-  GetNetworkService()->GetTotalNetworkUsages(base::BindOnce(
-      [](std::vector<network::mojom::NetworkUsagePtr>* p_network_usages,
-         base::OnceClosure quit_closure,
-         std::vector<network::mojom::NetworkUsagePtr> returned_usages) {
-        *p_network_usages = std::move(returned_usages);
-        std::move(quit_closure).Run();
-      },
-      base::Unretained(&network_usages), run_loop.QuitClosure()));
-  run_loop.Run();
-  return network_usages;
-}
-
-bool CheckContainsProcessID(
-    const std::vector<network::mojom::NetworkUsagePtr>& usages,
-    int process_id) {
-  for (const auto& usage : usages) {
-    if ((int)usage->process_id == process_id)
-      return true;
-  }
-  return false;
-}
-
-// Wait until |condition| returns true.
-void WaitForCondition(base::RepeatingCallback<bool()> condition) {
-  while (!condition.Run()) {
-    base::RunLoop run_loop;
-    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
-    run_loop.Run();
-  }
-}
-
 class ServiceWorkerStatusObserver : public ServiceWorkerContextCoreObserver {
  public:
   void WaitForStopped() {
@@ -980,48 +945,6 @@
   EXPECT_TRUE(service->worker_hosts_.empty());
 }
 
-// Make sure the entry in |NetworkService::GetTotalNetworkUsages()| was cleared
-// after process closed.
-IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
-                       GetNetworkUsagesClosed) {
-  if (IsInProcessNetworkService())
-    return;
-  EXPECT_TRUE(NavigateToURL(shell(), GetTestURL()));
-  Shell* shell2 = CreateBrowser();
-  EXPECT_TRUE(NavigateToURL(shell2, GetTestURL()));
-
-  int process_id1 =
-      shell()->web_contents()->GetMainFrame()->GetProcess()->GetID();
-  int process_id2 =
-      shell2->web_contents()->GetMainFrame()->GetProcess()->GetID();
-
-  // Load resource on the renderer to make sure the traffic was recorded.
-  EXPECT_TRUE(CheckCanLoadHttp(shell(), "/title2.html"));
-  EXPECT_TRUE(CheckCanLoadHttp(shell2, "/title3.html"));
-
-  // Both processes should have traffic recorded.
-  auto network_usages = GetTotalNetworkUsages();
-  EXPECT_TRUE(CheckContainsProcessID(network_usages, process_id1));
-  EXPECT_TRUE(CheckContainsProcessID(network_usages, process_id2));
-
-  // Closing |shell2| should cause the entry to be cleared.
-  shell2->Close();
-  shell2 = nullptr;
-
-  // Wait until the Network Service has noticed the change. We don't have a
-  // better way to force a flush on the Network Service side.
-  WaitForCondition(base::BindRepeating(
-      [](int process_id) {
-        auto usages = GetTotalNetworkUsages();
-        return !CheckContainsProcessID(usages, process_id);
-      },
-      process_id2));
-
-  network_usages = GetTotalNetworkUsages();
-  EXPECT_TRUE(CheckContainsProcessID(network_usages, process_id1));
-  EXPECT_FALSE(CheckContainsProcessID(network_usages, process_id2));
-}
-
 // Make sure that kSSLKeyLogFileHistogram is correctly recorded when the
 // network service instance is started and the SSLKEYLOGFILE env var is set or
 // the "--ssl-key-log-file" arg is set.
@@ -1068,43 +991,6 @@
   }
 }
 
-// Make sure |NetworkService::GetTotalNetworkUsages()| continues to work after
-// crash. See 'network_usage_accumulator_unittest' for quantified tests.
-IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
-                       GetNetworkUsagesCrashed) {
-  if (IsInProcessNetworkService())
-    return;
-  EXPECT_TRUE(NavigateToURL(shell(), GetTestURL()));
-  Shell* shell2 = CreateBrowser();
-  EXPECT_TRUE(NavigateToURL(shell2, GetTestURL()));
-
-  int process_id1 =
-      shell()->web_contents()->GetMainFrame()->GetProcess()->GetID();
-  int process_id2 =
-      shell2->web_contents()->GetMainFrame()->GetProcess()->GetID();
-
-  // Load resource on the renderer to make sure the traffic was recorded.
-  EXPECT_TRUE(CheckCanLoadHttp(shell(), "/title2.html"));
-  EXPECT_TRUE(CheckCanLoadHttp(shell2, "/title3.html"));
-
-  // Both processes should have traffic recorded.
-  auto network_usages = GetTotalNetworkUsages();
-  EXPECT_TRUE(CheckContainsProcessID(network_usages, process_id1));
-  EXPECT_TRUE(CheckContainsProcessID(network_usages, process_id2));
-
-  // Crashing Network Service should cause all entries to be cleared.
-  SimulateNetworkServiceCrash();
-  network_usages = GetTotalNetworkUsages();
-  EXPECT_FALSE(CheckContainsProcessID(network_usages, process_id1));
-  EXPECT_FALSE(CheckContainsProcessID(network_usages, process_id2));
-
-  // Should still be able to recored new traffic after crash.
-  EXPECT_TRUE(CheckCanLoadHttp(shell(), "/title2.html"));
-  network_usages = GetTotalNetworkUsages();
-  EXPECT_TRUE(CheckContainsProcessID(network_usages, process_id1));
-  EXPECT_FALSE(CheckContainsProcessID(network_usages, process_id2));
-}
-
 // Make sure cookie access doesn't hang or fail after a network process crash.
 IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, Cookies) {
   if (IsInProcessNetworkService())
diff --git a/content/browser/notifications/notification_event_dispatcher_impl.cc b/content/browser/notifications/notification_event_dispatcher_impl.cc
index 2e419f9..1e49c53 100644
--- a/content/browser/notifications/notification_event_dispatcher_impl.cc
+++ b/content/browser/notifications/notification_event_dispatcher_impl.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/callback_helpers.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/optional.h"
 #include "build/build_config.h"
 #include "content/browser/notifications/devtools_event_logging.h"
@@ -25,6 +26,9 @@
 namespace {
 
 using NotificationDispatchCompleteCallback =
+    base::OnceCallback<void(PersistentNotificationStatus,
+                            blink::ServiceWorkerStatusCode)>;
+using PersistentNotificationDispatchCompleteCallback =
     base::OnceCallback<void(PersistentNotificationStatus)>;
 using NotificationOperationCallback =
     base::OnceCallback<void(const ServiceWorkerRegistration*,
@@ -88,7 +92,8 @@
   RunOrPostTaskOnThread(
       FROM_HERE, BrowserThread::UI,
       base::BindOnce(std::move(dispatch_complete_callback),
-                     ConvertServiceWorkerStatus(service_worker_status)));
+                     ConvertServiceWorkerStatus(service_worker_status),
+                     service_worker_status));
 }
 
 // Dispatches the given notification action event on
@@ -149,7 +154,8 @@
   }
 
   GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(std::move(dispatch_complete_callback), status));
+      FROM_HERE, base::BindOnce(std::move(dispatch_complete_callback), status,
+                                service_worker_status));
 }
 
 // Finds the ServiceWorkerRegistration associated with the |origin| and
@@ -170,7 +176,8 @@
 #endif
   if (!success) {
     std::move(dispatch_complete_callback)
-        .Run(PersistentNotificationStatus::kDatabaseError);
+        .Run(PersistentNotificationStatus::kDatabaseError,
+             blink::ServiceWorkerStatusCode::kOk);
     return;
   }
 
@@ -276,7 +283,7 @@
               : PersistentNotificationStatus::kDatabaseError;
   if (service_worker_status != blink::ServiceWorkerStatusCode::kOk)
     status = ConvertServiceWorkerStatus(service_worker_status);
-  std::move(dispatch_complete_callback).Run(status);
+  std::move(dispatch_complete_callback).Run(status, service_worker_status);
 }
 
 // Called when the persistent notification close event has been handled
@@ -379,6 +386,28 @@
       std::move(dispatch_complete_callback));
 }
 
+void OnDispatchNotificationClickEventComplete(
+    PersistentNotificationDispatchCompleteCallback dispatch_complete_callback,
+    PersistentNotificationStatus status,
+    blink::ServiceWorkerStatusCode service_worker_status) {
+  base::UmaHistogramEnumeration(
+      "Notifications.PersistentWebNotificationClickEventResult",
+      service_worker_status);
+
+  std::move(dispatch_complete_callback).Run(status);
+}
+
+void OnDispatchNotificationCloseEventComplete(
+    PersistentNotificationDispatchCompleteCallback dispatch_complete_callback,
+    PersistentNotificationStatus status,
+    blink::ServiceWorkerStatusCode service_worker_status) {
+  base::UmaHistogramEnumeration(
+      "Notifications.PersistentWebNotificationCloseEventResult",
+      service_worker_status);
+
+  std::move(dispatch_complete_callback).Run(status);
+}
+
 }  // namespace
 
 // static
@@ -412,7 +441,8 @@
   DispatchNotificationEvent(
       browser_context, notification_id, origin, interaction,
       base::BindOnce(&DoDispatchNotificationClickEvent, action_index, reply),
-      std::move(dispatch_complete_callback));
+      base::BindOnce(&OnDispatchNotificationClickEventComplete,
+                     std::move(dispatch_complete_callback)));
 }
 
 void NotificationEventDispatcherImpl::DispatchNotificationCloseEvent(
@@ -423,11 +453,13 @@
     NotificationDispatchCompleteCallback dispatch_complete_callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  DispatchNotificationEvent(browser_context, notification_id, origin,
-                            PlatformNotificationContext::Interaction::CLOSED,
-                            base::BindOnce(&DoDispatchNotificationCloseEvent,
-                                           notification_id, by_user),
-                            std::move(dispatch_complete_callback));
+  DispatchNotificationEvent(
+      browser_context, notification_id, origin,
+      PlatformNotificationContext::Interaction::CLOSED,
+      base::BindOnce(&DoDispatchNotificationCloseEvent, notification_id,
+                     by_user),
+      base::BindOnce(&OnDispatchNotificationCloseEventComplete,
+                     std::move(dispatch_complete_callback)));
 }
 
 void NotificationEventDispatcherImpl::RegisterNonPersistentNotificationListener(
diff --git a/content/browser/prerender/prerender_host.cc b/content/browser/prerender/prerender_host.cc
index 18292e2..6ca0781 100644
--- a/content/browser/prerender/prerender_host.cc
+++ b/content/browser/prerender/prerender_host.cc
@@ -203,13 +203,10 @@
     // Activate the prerendered contents.
     WebContentsDelegate* delegate = current_web_contents->GetDelegate();
     DCHECK(delegate);
+    DCHECK(web_contents_);
     DCHECK(GetMainFrame()->frame_tree()->is_prerendering());
     GetMainFrame()->frame_tree()->ActivatePrerenderedFrameTree();
 
-    DCHECK(web_contents_);
-    auto* successor_web_contents =
-        static_cast<WebContentsImpl*>(web_contents_.get());
-
     // Tentatively use Portal's activation function.
     // TODO(https://crbug.com/1132746): Replace this with the MPArch.
     std::unique_ptr<WebContents> predecessor_web_contents =
@@ -218,11 +215,6 @@
 
     // Stop loading on the predecessor WebContents.
     predecessor_web_contents->Stop();
-
-    // Notify the renderer of activation to update the prerendering state and
-    // dispatch the prerenderingchange event.
-    successor_web_contents->NotifyPrerenderingPageActivated();
-
     return true;
   }
 
diff --git a/content/browser/renderer_host/input/synthetic_gesture_controller.cc b/content/browser/renderer_host/input/synthetic_gesture_controller.cc
index 0e1ea7a..6492297 100644
--- a/content/browser/renderer_host/input/synthetic_gesture_controller.cc
+++ b/content/browser/renderer_host/input/synthetic_gesture_controller.cc
@@ -66,9 +66,7 @@
 
 void SyntheticGestureController::QueueSyntheticGestureCompleteImmediately(
     std::unique_ptr<SyntheticGesture> synthetic_gesture) {
-  QueueSyntheticGesture(std::move(synthetic_gesture),
-                        base::BindOnce([](SyntheticGesture::Result result) {}),
-                        true);
+  QueueSyntheticGesture(std::move(synthetic_gesture), base::DoNothing(), true);
 }
 
 void SyntheticGestureController::QueueSyntheticGesture(
diff --git a/content/browser/renderer_host/input/touch_action_browsertest.cc b/content/browser/renderer_host/input/touch_action_browsertest.cc
index efd7d14..790a112 100644
--- a/content/browser/renderer_host/input/touch_action_browsertest.cc
+++ b/content/browser/renderer_host/input/touch_action_browsertest.cc
@@ -258,9 +258,8 @@
 
     std::unique_ptr<SyntheticSmoothScrollGesture> gesture1(
         new SyntheticSmoothScrollGesture(params1));
-    GetWidgetHost()->QueueSyntheticGesture(
-        std::move(gesture1),
-        base::BindOnce([](SyntheticGesture::Result result) {}));
+    GetWidgetHost()->QueueSyntheticGesture(std::move(gesture1),
+                                           base::DoNothing());
 
     JankMainThread(kLongJankTime);
     GiveItSomeTime(800);
diff --git a/content/browser/renderer_host/media/media_devices_dispatcher_host_unittest.cc b/content/browser/renderer_host/media/media_devices_dispatcher_host_unittest.cc
index c35698d..390c4d22 100644
--- a/content/browser/renderer_host/media/media_devices_dispatcher_host_unittest.cc
+++ b/content/browser/renderer_host/media/media_devices_dispatcher_host_unittest.cc
@@ -56,7 +56,7 @@
 const char* const kDefaultVideoDeviceID = kZeroResolutionVideoDeviceID;
 const char kDefaultAudioDeviceID[] = "fake_audio_input_2";
 
-const auto kIgnoreLogMessageCB = base::BindRepeating([](const std::string&) {});
+const auto kIgnoreLogMessageCB = base::DoNothing();
 
 void PhysicalDevicesEnumerated(base::OnceClosure quit_closure,
                                MediaDeviceEnumeration* out,
diff --git a/content/browser/renderer_host/media/media_devices_manager_unittest.cc b/content/browser/renderer_host/media/media_devices_manager_unittest.cc
index de4d877..2636b69 100644
--- a/content/browser/renderer_host/media/media_devices_manager_unittest.cc
+++ b/content/browser/renderer_host/media/media_devices_manager_unittest.cc
@@ -52,7 +52,7 @@
 
 const size_t kNumAudioInputDevices = 2;
 
-const auto kIgnoreLogMessageCB = base::BindRepeating([](const std::string&) {});
+const auto kIgnoreLogMessageCB = base::DoNothing();
 
 MediaDeviceSaltAndOrigin GetSaltAndOrigin(int /* process_id */,
                                           int /* frame_id */) {
diff --git a/content/browser/renderer_host/media/service_video_capture_provider_unittest.cc b/content/browser/renderer_host/media/service_video_capture_provider_unittest.cc
index 82bf2ec..58c1e1a8 100644
--- a/content/browser/renderer_host/media/service_video_capture_provider_unittest.cc
+++ b/content/browser/renderer_host/media/service_video_capture_provider_unittest.cc
@@ -39,8 +39,7 @@
 static const std::string kStubDeviceId = "StubDevice";
 static const media::VideoCaptureParams kArbitraryParams;
 static const base::WeakPtr<media::VideoFrameReceiver> kNullReceiver;
-static const auto kIgnoreLogMessageCB =
-    base::BindRepeating([](const std::string&) {});
+static const auto kIgnoreLogMessageCB = base::DoNothing();
 
 class MockVideoCaptureDeviceLauncherCallbacks
     : public VideoCaptureDeviceLauncher::Callbacks {
@@ -323,11 +322,9 @@
 
   // Make initial call to GetDeviceInfosAsync(). The service does not yet
   // respond.
-  provider_->GetDeviceInfosAsync(base::BindRepeating(
-      [](const std::vector<media::VideoCaptureDeviceInfo>&) {}));
+  provider_->GetDeviceInfosAsync(base::DoNothing());
   // Make an additional call to GetDeviceInfosAsync().
-  provider_->GetDeviceInfosAsync(base::BindRepeating(
-      [](const std::vector<media::VideoCaptureDeviceInfo>&) {}));
+  provider_->GetDeviceInfosAsync(base::DoNothing());
   {
     base::RunLoop give_mojo_chance_to_process;
     give_mojo_chance_to_process.RunUntilIdle();
diff --git a/content/browser/renderer_host/media/video_capture_manager_unittest.cc b/content/browser/renderer_host/media/video_capture_manager_unittest.cc
index 9391605..677f9ee 100644
--- a/content/browser/renderer_host/media/video_capture_manager_unittest.cc
+++ b/content/browser/renderer_host/media/video_capture_manager_unittest.cc
@@ -47,7 +47,7 @@
 
 namespace {
 
-const auto kIgnoreLogMessageCB = base::BindRepeating([](const std::string&) {});
+const auto kIgnoreLogMessageCB = base::DoNothing();
 
 // Wraps FakeVideoCaptureDeviceFactory to allow mocking of the
 // VideoCaptureDevice MaybeSuspend() and Resume() methods. This is used to check
@@ -261,10 +261,8 @@
     screenlock_monitor_ = std::make_unique<ScreenlockMonitor>(
         std::unique_ptr<ScreenlockMonitorSource>(screenlock_monitor_source_));
 
-    vcm_ =
-        new VideoCaptureManager(std::move(video_capture_provider),
-                                base::BindRepeating([](const std::string&) {}),
-                                ScreenlockMonitor::Get());
+    vcm_ = new VideoCaptureManager(std::move(video_capture_provider),
+                                   base::DoNothing(), ScreenlockMonitor::Get());
     const int32_t kNumberOfFakeDevices = 2;
     video_capture_device_factory_->SetToDefaultDevicesConfig(
         kNumberOfFakeDevices);
diff --git a/content/browser/renderer_host/navigation_controller_impl_unittest.cc b/content/browser/renderer_host/navigation_controller_impl_unittest.cc
index 0ff6166..de692328 100644
--- a/content/browser/renderer_host/navigation_controller_impl_unittest.cc
+++ b/content/browser/renderer_host/navigation_controller_impl_unittest.cc
@@ -107,7 +107,6 @@
               (override));
   MOCK_METHOD(void, AudioStateChanged, (bool is_audio_playing), (override));
   MOCK_METHOD(void, SetInsidePortal, (bool is_inside_portal), (override));
-  MOCK_METHOD(void, ActivatePrerender, (), (override));
   MOCK_METHOD(void,
               UpdateWebPreferences,
               (const ::blink::web_pref::WebPreferences& preferences),
diff --git a/content/browser/renderer_host/render_process_host_browsertest.cc b/content/browser/renderer_host/render_process_host_browsertest.cc
index f4fe588f..141c42d6 100644
--- a/content/browser/renderer_host/render_process_host_browsertest.cc
+++ b/content/browser/renderer_host/render_process_host_browsertest.cc
@@ -498,12 +498,12 @@
       const GURL& site) override {
     // Override for |site_to_isolate_|.
     if (site == site_to_isolate_) {
-      return StoragePartitionConfig::Create("blah_isolated_storage",
-                                            "blah_isolated_storage",
-                                            false /* in_memory */);
+      return StoragePartitionConfig::Create(
+          browser_context, "blah_isolated_storage", "blah_isolated_storage",
+          false /* in_memory */);
     }
 
-    return StoragePartitionConfig::CreateDefault();
+    return StoragePartitionConfig::CreateDefault(browser_context);
   }
 
   std::string GetStoragePartitionIdForSite(BrowserContext* browser_context,
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 2365c88..1b7c03f 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -390,40 +390,10 @@
 class RendererSandboxedProcessLauncherDelegate
     : public SandboxedProcessLauncherDelegate {
  public:
-  RendererSandboxedProcessLauncherDelegate()
-#if defined(OS_WIN)
-      : renderer_code_integrity_enabled_(
-            GetContentClient()->browser()->IsRendererCodeIntegrityEnabled())
-#endif
-  {
-  }
+  RendererSandboxedProcessLauncherDelegate() = default;
 
   ~RendererSandboxedProcessLauncherDelegate() override = default;
 
-#if defined(OS_WIN)
-  bool PreSpawnTarget(sandbox::TargetPolicy* policy) override {
-    sandbox::policy::SandboxWin::AddBaseHandleClosePolicy(policy);
-
-    const std::wstring& sid =
-        GetContentClient()->browser()->GetAppContainerSidForSandboxType(
-            GetSandboxType());
-    if (!sid.empty())
-      sandbox::policy::SandboxWin::AddAppContainerPolicy(policy, sid.c_str());
-    ContentBrowserClient::ChildSpawnFlags flags(
-        ContentBrowserClient::ChildSpawnFlags::NONE);
-    if (renderer_code_integrity_enabled_)
-      flags = ContentBrowserClient::ChildSpawnFlags::RENDERER_CODE_INTEGRITY;
-    return GetContentClient()->browser()->PreSpawnChild(
-        policy, sandbox::policy::SandboxType::kRenderer, flags);
-  }
-
-  bool CetCompatible() override {
-    // Disable CET for renderer because v8 deoptimization swaps stacks in a
-    // non-compliant way.
-    return false;
-  }
-#endif  // OS_WIN
-
 #if BUILDFLAG(USE_ZYGOTE_HANDLE)
   ZygoteHandle GetZygote() override {
     const base::CommandLine& browser_command_line =
@@ -443,12 +413,74 @@
   sandbox::policy::SandboxType GetSandboxType() override {
     return sandbox::policy::SandboxType::kRenderer;
   }
+};
 
 #if defined(OS_WIN)
+// NOTE: changes to this class need to be reviewed by the security team.
+class RendererSandboxedProcessLauncherDelegateWin
+    : public RendererSandboxedProcessLauncherDelegate {
+ public:
+  RendererSandboxedProcessLauncherDelegateWin(base::CommandLine* cmd_line)
+      : renderer_code_integrity_enabled_(
+            GetContentClient()->browser()->IsRendererCodeIntegrityEnabled()) {
+    if (cmd_line->HasSwitch(switches::kJavaScriptFlags)) {
+      std::string js_flags =
+          cmd_line->GetSwitchValueASCII(switches::kJavaScriptFlags);
+      std::vector<base::StringPiece> js_flag_list = base::SplitStringPiece(
+          js_flags, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+      for (const auto& js_flag : js_flag_list) {
+        if (js_flag.as_string() == "--jitless") {
+          // If v8 is running jitless then there is no need for the ability to
+          // mark writable pages as executable to be available to the process.
+          dynamic_code_can_be_disabled_ = true;
+          break;
+        }
+      }
+    }
+  }
+
+  bool PreSpawnTarget(sandbox::TargetPolicy* policy) override {
+    sandbox::policy::SandboxWin::AddBaseHandleClosePolicy(policy);
+
+    const std::wstring& sid =
+        GetContentClient()->browser()->GetAppContainerSidForSandboxType(
+            GetSandboxType());
+    if (!sid.empty())
+      sandbox::policy::SandboxWin::AddAppContainerPolicy(policy, sid.c_str());
+
+    ContentBrowserClient::ChildSpawnFlags flags(
+        ContentBrowserClient::ChildSpawnFlags::NONE);
+    if (renderer_code_integrity_enabled_) {
+      flags = ContentBrowserClient::ChildSpawnFlags::RENDERER_CODE_INTEGRITY;
+
+      // If the renderer process is protected by code integrity, more
+      // mitigations become available.
+      if (dynamic_code_can_be_disabled_) {
+        sandbox::MitigationFlags mitigation_flags =
+            policy->GetDelayedProcessMitigations();
+        mitigation_flags |= sandbox::MITIGATION_DYNAMIC_CODE_DISABLE;
+        if (sandbox::SBOX_ALL_OK !=
+            policy->SetDelayedProcessMitigations(mitigation_flags)) {
+          return false;
+        }
+      }
+    }
+
+    return GetContentClient()->browser()->PreSpawnChild(
+        policy, sandbox::policy::SandboxType::kRenderer, flags);
+  }
+
+  bool CetCompatible() override {
+    // Disable CET for renderer because v8 deoptimization swaps stacks in a
+    // non-compliant way.
+    return false;
+  }
+
  private:
   const bool renderer_code_integrity_enabled_;
-#endif
+  bool dynamic_code_can_be_disabled_ = false;
 };
+#endif  // defined(OS_WIN)
 
 const char kSessionStorageHolderKey[] = "kSessionStorageHolderKey";
 
@@ -1796,12 +1828,20 @@
       cmd_line->PrependWrapper(renderer_prefix);
     AppendRendererCommandLine(cmd_line.get());
 
+#if defined(OS_WIN)
+    std::unique_ptr<SandboxedProcessLauncherDelegate> sandbox_delegate =
+        std::make_unique<RendererSandboxedProcessLauncherDelegateWin>(
+            cmd_line.get());
+#else
+    std::unique_ptr<SandboxedProcessLauncherDelegate> sandbox_delegate =
+        std::make_unique<RendererSandboxedProcessLauncherDelegate>();
+#endif
     // Spawn the child process asynchronously to avoid blocking the UI thread.
     // As long as there's no renderer prefix, we can use the zygote process
     // at this stage.
     child_process_launcher_ = std::make_unique<ChildProcessLauncher>(
-        std::make_unique<RendererSandboxedProcessLauncherDelegate>(),
-        std::move(cmd_line), GetID(), this, std::move(mojo_invitation_),
+        std::move(sandbox_delegate), std::move(cmd_line), GetID(), this,
+        std::move(mojo_invitation_),
         base::BindRepeating(&RenderProcessHostImpl::OnMojoError, id_),
         GetV8SnapshotFilesToPreload());
     channel_->Pause();
diff --git a/content/browser/renderer_host/render_process_host_unittest.cc b/content/browser/renderer_host/render_process_host_unittest.cc
index ae7882a..a9ac048 100644
--- a/content/browser/renderer_host/render_process_host_unittest.cc
+++ b/content/browser/renderer_host/render_process_host_unittest.cc
@@ -851,11 +851,12 @@
       BrowserContext* browser_context,
       const GURL& site) override {
     if (site == site_) {
-      return StoragePartitionConfig::Create(partition_domain_, partition_name_,
+      return StoragePartitionConfig::Create(browser_context, partition_domain_,
+                                            partition_name_,
                                             false /* in_memory */);
     }
 
-    return StoragePartitionConfig::CreateDefault();
+    return StoragePartitionConfig::CreateDefault(browser_context);
   }
 
   GURL site_;
diff --git a/content/browser/scheduler/browser_task_queues_unittest.cc b/content/browser/scheduler/browser_task_queues_unittest.cc
index c3a7fcca..e6441fde 100644
--- a/content/browser/scheduler/browser_task_queues_unittest.cc
+++ b/content/browser/scheduler/browser_task_queues_unittest.cc
@@ -261,9 +261,8 @@
   queues_.reset();
 
   for (size_t i = 0; i < BrowserTaskQueues::kNumQueueTypes; ++i) {
-    EXPECT_FALSE(
-        handle_->GetBrowserTaskRunner(static_cast<QueueType>(i))
-            ->PostTask(FROM_HERE, base::BindLambdaForTesting([]() {})));
+    EXPECT_FALSE(handle_->GetBrowserTaskRunner(static_cast<QueueType>(i))
+                     ->PostTask(FROM_HERE, base::DoNothing()));
   }
 
   RunLoop run_loop;
diff --git a/content/browser/scheduler/responsiveness/watcher_unittest.cc b/content/browser/scheduler/responsiveness/watcher_unittest.cc
index c1faaa5..7384d7e6 100644
--- a/content/browser/scheduler/responsiveness/watcher_unittest.cc
+++ b/content/browser/scheduler/responsiveness/watcher_unittest.cc
@@ -321,12 +321,10 @@
 
 TEST_F(ResponsivenessWatcherRealIOThreadTest, MessageLoopObserver) {
   // Post a do-nothing task onto the UI thread.
-  content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
-                                               base::BindOnce([]() {}));
+  content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, base::DoNothing());
 
   // Post a do-nothing task onto the IO thread.
-  content::GetIOThreadTaskRunner({})->PostTask(FROM_HERE,
-                                               base::BindOnce([]() {}));
+  content::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, base::DoNothing());
 
   // Post a task onto the IO thread that hops back to the UI thread. This
   // guarantees that both of the do-nothing tasks have already been processed.
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index 68badea..1abc9f1 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -1828,8 +1828,7 @@
   params.prevent_fling = false;
   RenderWidgetHostImpl* root_widget_host =
       static_cast<RenderWidgetHostImpl*>(root_rwhv->GetRenderWidgetHost());
-  auto dont_care_on_complete =
-      base::BindOnce([](SyntheticGesture::Result result) {});
+  auto dont_care_on_complete = base::DoNothing();
   root_widget_host->QueueSyntheticGesture(
       std::make_unique<SyntheticSmoothScrollGesture>(params),
       std::move(dont_care_on_complete));
diff --git a/content/browser/sms/webotp_service_unittest.cc b/content/browser/sms/webotp_service_unittest.cc
index c360806..282413f 100644
--- a/content/browser/sms/webotp_service_unittest.cc
+++ b/content/browser/sms/webotp_service_unittest.cc
@@ -873,8 +873,7 @@
     service.ActivateTimer();
   }));
 
-  service.MakeRequest(BindLambdaForTesting(
-      [](SmsStatus status, const Optional<string>& otp) {}));
+  service.MakeRequest(base::DoNothing());
 
   ukm_loop.Run();
 
@@ -893,8 +892,7 @@
     loop.Quit();
   }));
 
-  service.MakeRequest(BindLambdaForTesting(
-      [](SmsStatus status, const Optional<string>& otp) {}));
+  service.MakeRequest(base::DoNothing());
 
   loop.Run();
   ExpectNoOutcomeUKM();
@@ -915,8 +913,7 @@
     service.ActivateTimer();
   }));
 
-  service.MakeRequest(BindLambdaForTesting(
-      [](SmsStatus status, const Optional<string>& otp) {}));
+  service.MakeRequest(base::DoNothing());
 
   ukm_loop.Run();
 
@@ -938,8 +935,7 @@
     loop.Quit();
   }));
 
-  service.MakeRequest(BindLambdaForTesting(
-      [](SmsStatus status, const Optional<string>& otp) {}));
+  service.MakeRequest(base::DoNothing());
 
   loop.Run();
   ExpectNoOutcomeUKM();
@@ -961,8 +957,7 @@
     service.DismissPrompt();
   }));
 
-  service.MakeRequest(BindLambdaForTesting(
-      [](SmsStatus status, const Optional<string>& otp) {}));
+  service.MakeRequest(base::DoNothing());
 
   ukm_loop.Run();
 
@@ -1045,8 +1040,7 @@
       service.DismissPrompt();
     }));
 
-    service.MakeRequest(BindLambdaForTesting(
-        [](SmsStatus status, const Optional<string>& otp) {}));
+    service.MakeRequest(base::DoNothing());
 
     ukm_loop.Run();
   }
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 7ba2b49..e6f8072 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -1762,6 +1762,17 @@
   std::move(callback).Run();
 }
 
+void StoragePartitionImpl::OnDataUseUpdate(
+    int32_t network_traffic_annotation_id_hash,
+    int64_t recv_bytes,
+    int64_t sent_bytes) {
+  int process_id = url_loader_network_observers_.current_context().process_id;
+  int routing_id = url_loader_network_observers_.current_context().routing_id;
+  GetContentClient()->browser()->OnNetworkServiceDataUseUpdate(
+      process_id, routing_id, network_traffic_annotation_id_hash, recv_bytes,
+      sent_bytes);
+}
+
 void StoragePartitionImpl::Clone(
     mojo::PendingReceiver<network::mojom::URLLoaderNetworkServiceObserver>
         observer) {
diff --git a/content/browser/storage_partition_impl.h b/content/browser/storage_partition_impl.h
index 86669606..39ad00eb 100644
--- a/content/browser/storage_partition_impl.h
+++ b/content/browser/storage_partition_impl.h
@@ -296,6 +296,9 @@
                        OnClearSiteDataCallback callback) override;
   void OnLoadingStateUpdate(network::mojom::LoadInfoPtr info,
                             OnLoadingStateUpdateCallback callback) override;
+  void OnDataUseUpdate(int32_t network_traffic_annotation_id_hash,
+                       int64_t recv_bytes,
+                       int64_t sent_bytes) override;
 
   scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter() {
     return url_loader_factory_getter_;
diff --git a/content/browser/storage_partition_impl_map_unittest.cc b/content/browser/storage_partition_impl_map_unittest.cc
index 12d55ae..94242c96 100644
--- a/content/browser/storage_partition_impl_map_unittest.cc
+++ b/content/browser/storage_partition_impl_map_unittest.cc
@@ -50,15 +50,15 @@
 }
 
 TEST(StoragePartitionImplMapTest, AppCacheCleanup) {
-  const auto kOnDiskConfig = content::StoragePartitionConfig::Create(
-      "foo", /*partition_name=*/"", /*in_memory=*/false);
-
   base::test::ScopedFeatureList f;
   f.InitAndDisableFeature(blink::features::kAppCache);
   BrowserTaskEnvironment task_environment;
   TestBrowserContext browser_context;
   base::FilePath appcache_path;
 
+  const auto kOnDiskConfig = content::StoragePartitionConfig::Create(
+      &browser_context, "foo", /*partition_name=*/"", /*in_memory=*/false);
+
   {
     // Creating the partition in the map also does the deletion, so
     // create it once, so we can find out what path the partition
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index ffabd70..aa4ae9d 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -2378,15 +2378,6 @@
   GetDelegate()->WebContentsBecamePortal(predecessor_web_contents);
 }
 
-void WebContentsImpl::NotifyPrerenderingPageActivated() {
-  OPTIONAL_TRACE_EVENT0("content",
-                        "WebContentsImpl::NotifyPrerenderingPageActivated");
-  ExecutePageBroadcastMethod(base::BindRepeating([](RenderViewHostImpl* rvh) {
-    if (auto& broadcast = rvh->GetAssociatedPageBroadcast())
-      broadcast->ActivatePrerender();
-  }));
-}
-
 void WebContentsImpl::NotifyInsidePortal(bool inside_portal) {
   OPTIONAL_TRACE_EVENT1("content", "WebContentsImpl::NotifyInsidePortal",
                         "inside_portal", inside_portal);
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 91c7bb1..ff48d4c 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -1206,10 +1206,6 @@
   void DidActivatePortal(WebContentsImpl* predecessor_web_contents,
                          base::TimeTicks activation_time);
 
-  // Sends a page message to notify every process in the frame tree if the
-  // prerendering page is activated.
-  void NotifyPrerenderingPageActivated();
-
   // Notifies observers that AppCache was accessed. Public so AppCache code can
   // call this directly.
   void OnAppCacheAccessed(const GURL& manifest_url, bool blocked_by_policy);
diff --git a/content/browser/web_contents_receiver_set_browsertest.cc b/content/browser/web_contents_receiver_set_browsertest.cc
index 394af14..c7cf6de 100644
--- a/content/browser/web_contents_receiver_set_browsertest.cc
+++ b/content/browser/web_contents_receiver_set_browsertest.cc
@@ -137,7 +137,7 @@
   // Verify that this message never reaches the binding for the old frame. If it
   // does, the impl will hit a DCHECK. The RunLoop terminates when the client is
   // disconnected.
-  override_client->Ping(base::BindOnce([] {}));
+  override_client->Ping(base::DoNothing());
   run_loop.Run();
 }
 
diff --git a/content/browser/web_database/web_database_host_impl_unittest.cc b/content/browser/web_database/web_database_host_impl_unittest.cc
index 27ddfedc..0828185 100644
--- a/content/browser/web_database/web_database_host_impl_unittest.cc
+++ b/content/browser/web_database/web_database_host_impl_unittest.cc
@@ -137,30 +137,28 @@
 
   CheckUnauthorizedOrigin([&]() {
     host()->OpenFile(bad_vfs_file_name,
-                     /*desired_flags=*/0, base::BindOnce([](base::File) {}));
+                     /*desired_flags=*/0, base::DoNothing());
   });
 
   CheckUnauthorizedOrigin([&]() {
     host()->DeleteFile(bad_vfs_file_name,
-                       /*sync_dir=*/false, base::BindOnce([](int32_t) {}));
+                       /*sync_dir=*/false, base::DoNothing());
   });
 
   CheckUnauthorizedOrigin([&]() {
-    host()->GetFileAttributes(bad_vfs_file_name,
-                              base::BindOnce([](int32_t) {}));
+    host()->GetFileAttributes(bad_vfs_file_name, base::DoNothing());
   });
 
-  CheckUnauthorizedOrigin([&]() {
-    host()->GetFileSize(bad_vfs_file_name, base::BindOnce([](int64_t) {}));
-  });
+  CheckUnauthorizedOrigin(
+      [&]() { host()->GetFileSize(bad_vfs_file_name, base::DoNothing()); });
 
   CheckUnauthorizedOrigin([&]() {
     host()->SetFileSize(bad_vfs_file_name, /*expected_size=*/0,
-                        base::BindOnce([](bool) {}));
+                        base::DoNothing());
   });
 
   CheckUnauthorizedOrigin([&]() {
-    host()->GetSpaceAvailable(incorrect_origin, base::BindOnce([](int64_t) {}));
+    host()->GetSpaceAvailable(incorrect_origin, base::DoNothing());
   });
 
   CheckUnauthorizedOrigin([&]() {
@@ -182,9 +180,8 @@
   const url::Origin opaque_origin;
   const base::string16 db_name(base::ASCIIToUTF16("db_name"));
 
-  CheckInvalidOrigin([&]() {
-    host()->GetSpaceAvailable(opaque_origin, base::BindOnce([](int64_t) {}));
-  });
+  CheckInvalidOrigin(
+      [&]() { host()->GetSpaceAvailable(opaque_origin, base::DoNothing()); });
 
   CheckInvalidOrigin([&]() {
     host()->Opened(opaque_origin, db_name, base::ASCIIToUTF16("description"),
diff --git a/content/browser/webrtc/resources/BUILD.gn b/content/browser/webrtc/resources/BUILD.gn
index 4eaa1d8..db220b8 100644
--- a/content/browser/webrtc/resources/BUILD.gn
+++ b/content/browser/webrtc/resources/BUILD.gn
@@ -3,11 +3,41 @@
 # found in the LICENSE file.
 
 import("//tools/grit/grit_rule.gni")
+import("//ui/webui/resources/tools/generate_grd.gni")
+
+generated_grd = "$target_gen_dir/resources.grd"
+
+generate_grd("build_grd") {
+  out_grd = generated_grd
+  grd_prefix = "webrtc_internals"
+  input_files_base_dir = rebase_path(".", "//")
+
+  input_files = [
+    "data_series.js",
+    "dump_creator.js",
+    "peer_connection_update_table.js",
+    "ssrc_info_manager.js",
+    "stats_graph_helper.js",
+    "stats_rates_calculator.js",
+    "stats_table.js",
+    "tab_view.js",
+    "timeline_graph_view.js",
+    "webrtc_internals.html",
+    "webrtc_internals.css",
+    "webrtc_internals.js",
+  ]
+}
 
 grit("resources") {
-  source = "resources.grd"
+  source = generated_grd
+  enable_input_discovery_for_gn_analyze = false
+  deps = [ ":build_grd" ]
+
   outputs = [
     "grit/webrtc_internals_resources.h",
+    "grit/webrtc_internals_resources_map.cc",
+    "grit/webrtc_internals_resources_map.h",
     "webrtc_internals_resources.pak",
   ]
+  output_dir = "$root_gen_dir/content/browser/webrtc/resources"
 }
diff --git a/content/browser/webrtc/resources/data_series.js b/content/browser/webrtc/resources/data_series.js
index dd0da7a..428d0dc 100644
--- a/content/browser/webrtc/resources/data_series.js
+++ b/content/browser/webrtc/resources/data_series.js
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// The maximum number of data points buffered for each stats. Old data points
+// will be shifted out when the buffer is full.
+export const MAX_STATS_DATA_POINT_BUFFER_SIZE = 1000;
+
 /**
  * A TimelineDataSeries collects an ordered series of (time, value) pairs,
  * and converts them to graph points.  It also keeps track of its color and
@@ -9,13 +13,8 @@
  * It keeps MAX_STATS_DATA_POINT_BUFFER_SIZE data points at most. Old data
  * points will be dropped when it reaches this size.
  */
-var TimelineDataSeries = (function() {
-  'use strict';
-
-  /**
-   * @constructor
-   */
-  function TimelineDataSeries() {
+export class TimelineDataSeries {
+  constructor() {
     // List of DataPoints in chronological order.
     this.dataPoints_ = [];
 
@@ -29,109 +28,106 @@
     this.cacheValues_ = [];
   }
 
-  TimelineDataSeries.prototype = {
-    /**
-     * @override
-     */
-    toJSON: function() {
-      if (this.dataPoints_.length < 1) {
-        return {};
-      }
-
-      var values = [];
-      for (var i = 0; i < this.dataPoints_.length; ++i) {
-        values.push(this.dataPoints_[i].value);
-      }
-      return {
-        startTime: this.dataPoints_[0].time,
-        endTime: this.dataPoints_[this.dataPoints_.length - 1].time,
-        values: JSON.stringify(values),
-      };
-    },
-
-    /**
-     * Adds a DataPoint to |this| with the specified time and value.
-     * DataPoints are assumed to be received in chronological order.
-     */
-    addPoint: function(timeTicks, value) {
-      var time = new Date(timeTicks);
-      this.dataPoints_.push(new DataPoint(time, value));
-
-      if (this.dataPoints_.length > MAX_STATS_DATA_POINT_BUFFER_SIZE) {
-        this.dataPoints_.shift();
-      }
-    },
-
-    isVisible: function() {
-      return this.isVisible_;
-    },
-
-    show: function(isVisible) {
-      this.isVisible_ = isVisible;
-    },
-
-    getColor: function() {
-      return this.color_;
-    },
-
-    setColor: function(color) {
-      this.color_ = color;
-    },
-
-    getCount: function() {
-      return this.dataPoints_.length;
-    },
-    /**
-     * Returns a list containing the values of the data series at |count|
-     * points, starting at |startTime|, and |stepSize| milliseconds apart.
-     * Caches values, so showing/hiding individual data series is fast.
-     */
-    getValues: function(startTime, stepSize, count) {
-      // Use cached values, if we can.
-      if (this.cacheStartTime_ === startTime &&
-          this.cacheStepSize_ === stepSize &&
-          this.cacheValues_.length === count) {
-        return this.cacheValues_;
-      }
-
-      // Do all the work.
-      this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count);
-      this.cacheStartTime_ = startTime;
-      this.cacheStepSize_ = stepSize;
-
-      return this.cacheValues_;
-    },
-
-    /**
-     * Returns the cached |values| in the specified time period.
-     */
-    getValuesInternal_: function(startTime, stepSize, count) {
-      var values = [];
-      var nextPoint = 0;
-      var currentValue = 0;
-      var time = startTime;
-      for (var i = 0; i < count; ++i) {
-        while (nextPoint < this.dataPoints_.length &&
-               this.dataPoints_[nextPoint].time < time) {
-          currentValue = this.dataPoints_[nextPoint].value;
-          ++nextPoint;
-        }
-        values[i] = currentValue;
-        time += stepSize;
-      }
-      return values;
+  /**
+   * @override
+   */
+  toJSON() {
+    if (this.dataPoints_.length < 1) {
+      return {};
     }
-  };
+
+    var values = [];
+    for (var i = 0; i < this.dataPoints_.length; ++i) {
+      values.push(this.dataPoints_[i].value);
+    }
+    return {
+      startTime: this.dataPoints_[0].time,
+      endTime: this.dataPoints_[this.dataPoints_.length - 1].time,
+      values: JSON.stringify(values),
+    };
+  }
 
   /**
-   * A single point in a data series.  Each point has a time, in the form of
-   * milliseconds since the Unix epoch, and a numeric value.
-   * @constructor
+   * Adds a DataPoint to |this| with the specified time and value.
+   * DataPoints are assumed to be received in chronological order.
    */
-  function DataPoint(time, value) {
+  addPoint(timeTicks, value) {
+    var time = new Date(timeTicks);
+    this.dataPoints_.push(new DataPoint(time, value));
+
+    if (this.dataPoints_.length > MAX_STATS_DATA_POINT_BUFFER_SIZE) {
+      this.dataPoints_.shift();
+    }
+  }
+
+  isVisible() {
+    return this.isVisible_;
+  }
+
+  show(isVisible) {
+    this.isVisible_ = isVisible;
+  }
+
+  getColor() {
+    return this.color_;
+  }
+
+  setColor(color) {
+    this.color_ = color;
+  }
+
+  getCount() {
+    return this.dataPoints_.length;
+  }
+  /**
+   * Returns a list containing the values of the data series at |count|
+   * points, starting at |startTime|, and |stepSize| milliseconds apart.
+   * Caches values, so showing/hiding individual data series is fast.
+   */
+  getValues(startTime, stepSize, count) {
+    // Use cached values, if we can.
+    if (this.cacheStartTime_ === startTime &&
+        this.cacheStepSize_ === stepSize &&
+        this.cacheValues_.length === count) {
+      return this.cacheValues_;
+    }
+
+    // Do all the work.
+    this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count);
+    this.cacheStartTime_ = startTime;
+    this.cacheStepSize_ = stepSize;
+
+    return this.cacheValues_;
+  }
+
+  /**
+   * Returns the cached |values| in the specified time period.
+   */
+  getValuesInternal_(startTime, stepSize, count) {
+    var values = [];
+    var nextPoint = 0;
+    var currentValue = 0;
+    var time = startTime;
+    for (var i = 0; i < count; ++i) {
+      while (nextPoint < this.dataPoints_.length &&
+             this.dataPoints_[nextPoint].time < time) {
+        currentValue = this.dataPoints_[nextPoint].value;
+        ++nextPoint;
+      }
+      values[i] = currentValue;
+      time += stepSize;
+    }
+    return values;
+  }
+}
+
+/**
+ * A single point in a data series.  Each point has a time, in the form of
+ * milliseconds since the Unix epoch, and a numeric value.
+ */
+class DataPoint {
+  constructor(time, value) {
     this.time = time;
     this.value = value;
   }
-
-  return TimelineDataSeries;
-})();
+}
diff --git a/content/browser/webrtc/resources/dump_creator.js b/content/browser/webrtc/resources/dump_creator.js
index 328d45c..39dd506 100644
--- a/content/browser/webrtc/resources/dump_creator.js
+++ b/content/browser/webrtc/resources/dump_creator.js
@@ -2,17 +2,26 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {$} from 'chrome://resources/js/util.m.js';
+
+/** A list of getUserMedia requests. */
+export var userMediaRequests = [];
+/** A map from peer connection id to the PeerConnectionRecord. */
+export var peerConnectionDataStore = {};
+
+// Also duplicating on window since tests access these from C++.
+window.userMediaRequests = userMediaRequests;
+window.peerConnectionDataStore = peerConnectionDataStore;
 
 /**
  * Provides the UI for dump creation.
  */
-var DumpCreator = (function() {
+export class DumpCreator {
   /**
    * @param {Element} containerElement The parent element of the dump creation
    *     UI.
-   * @constructor
    */
-  function DumpCreator(containerElement) {
+  constructor(containerElement) {
     /**
      * The root element of the dump creation UI.
      * @type {Element}
@@ -37,88 +46,85 @@
         'click', this.onEventLogRecordingsChanged_.bind(this));
   }
 
-  DumpCreator.prototype = {
-    // Mark the diagnostic audio recording checkbox checked.
-    setAudioDebugRecordingsCheckbox: function() {
-      this.root_.getElementsByTagName('input')[0].checked = true;
-    },
+  // Mark the diagnostic audio recording checkbox checked.
+  setAudioDebugRecordingsCheckbox() {
+    this.root_.getElementsByTagName('input')[0].checked = true;
+  }
 
-    // Mark the diagnostic audio recording checkbox unchecked.
-    clearAudioDebugRecordingsCheckbox: function() {
-      this.root_.getElementsByTagName('input')[0].checked = false;
-    },
+  // Mark the diagnostic audio recording checkbox unchecked.
+  clearAudioDebugRecordingsCheckbox() {
+    this.root_.getElementsByTagName('input')[0].checked = false;
+  }
 
-    // Mark the event log recording checkbox checked.
-    setEventLogRecordingsCheckbox: function() {
-      this.root_.getElementsByTagName('input')[1].checked = true;
-    },
+  // Mark the event log recording checkbox checked.
+  setEventLogRecordingsCheckbox() {
+    this.root_.getElementsByTagName('input')[1].checked = true;
+  }
 
-    // Mark the event log recording checkbox unchecked.
-    clearEventLogRecordingsCheckbox: function() {
-      this.root_.getElementsByTagName('input')[1].checked = false;
-    },
+  // Mark the event log recording checkbox unchecked.
+  clearEventLogRecordingsCheckbox() {
+    this.root_.getElementsByTagName('input')[1].checked = false;
+  }
 
-    // Mark the event log recording checkbox as mutable/immutable.
-    setEventLogRecordingsCheckboxMutability: function(mutable) {
-      // TODO(eladalon): Remove reliance on number and order of elements.
-      // https://crbug.com/817391
-      this.root_.getElementsByTagName('input')[1].disabled = !mutable;
-      if (!mutable) {
-        var label = this.root_.getElementsByTagName('label')[2];
-        label.style = 'color:red;';
-        label.textContent =
-            ' WebRTC event logging\'s state was set by a command line flag.';
-      }
-    },
+  // Mark the event log recording checkbox as mutable/immutable.
+  setEventLogRecordingsCheckboxMutability(mutable) {
+    // TODO(eladalon): Remove reliance on number and order of elements.
+    // https://crbug.com/817391
+    this.root_.getElementsByTagName('input')[1].disabled = !mutable;
+    if (!mutable) {
+      var label = this.root_.getElementsByTagName('label')[2];
+      label.style = 'color:red;';
+      label.textContent =
+          ' WebRTC event logging\'s state was set by a command line flag.';
+    }
+  }
 
-    /**
-     * Downloads the PeerConnection updates and stats data as a file.
-     *
-     * @private
-     */
-    onDownloadData_: function() {
-      var dumpObject = {
-        'getUserMedia': userMediaRequests,
-        'PeerConnections': peerConnectionDataStore,
-        'UserAgent': navigator.userAgent,
-      };
-      var textBlob = new Blob(
-          [JSON.stringify(dumpObject, null, 1)], {type: 'octet/stream'});
-      var URL = window.URL.createObjectURL(textBlob);
+  /**
+   * Downloads the PeerConnection updates and stats data as a file.
+   *
+   * @private
+   */
+  onDownloadData_() {
+    var dumpObject = {
+      'getUserMedia': userMediaRequests,
+      'PeerConnections': peerConnectionDataStore,
+      'UserAgent': navigator.userAgent,
+    };
+    var textBlob =
+        new Blob([JSON.stringify(dumpObject, null, 1)], {type: 'octet/stream'});
+    var URL = window.URL.createObjectURL(textBlob);
 
-      var anchor = this.root_.getElementsByTagName('a')[0];
-      anchor.href = URL;
-      anchor.download = 'webrtc_internals_dump.txt';
-      // The default action of the anchor will download the URL.
-    },
+    var anchor = this.root_.getElementsByTagName('a')[0];
+    anchor.href = URL;
+    anchor.download = 'webrtc_internals_dump.txt';
+    // The default action of the anchor will download the URL.
+  }
 
-    /**
-     * Handles the event of toggling the audio debug recordings state.
-     *
-     * @private
-     */
-    onAudioDebugRecordingsChanged_: function() {
-      var enabled = this.root_.getElementsByTagName('input')[0].checked;
-      if (enabled) {
-        chrome.send('enableAudioDebugRecordings');
-      } else {
-        chrome.send('disableAudioDebugRecordings');
-      }
-    },
+  /**
+   * Handles the event of toggling the audio debug recordings state.
+   *
+   * @private
+   */
+  onAudioDebugRecordingsChanged_() {
+    var enabled = this.root_.getElementsByTagName('input')[0].checked;
+    if (enabled) {
+      chrome.send('enableAudioDebugRecordings');
+    } else {
+      chrome.send('disableAudioDebugRecordings');
+    }
+  }
 
-    /**
-     * Handles the event of toggling the event log recordings state.
-     *
-     * @private
-     */
-    onEventLogRecordingsChanged_: function() {
-      var enabled = this.root_.getElementsByTagName('input')[1].checked;
-      if (enabled) {
-        chrome.send('enableEventLogRecordings');
-      } else {
-        chrome.send('disableEventLogRecordings');
-      }
-    },
-  };
-  return DumpCreator;
-})();
+  /**
+   * Handles the event of toggling the event log recordings state.
+   *
+   * @private
+   */
+  onEventLogRecordingsChanged_() {
+    var enabled = this.root_.getElementsByTagName('input')[1].checked;
+    if (enabled) {
+      chrome.send('enableEventLogRecordings');
+    } else {
+      chrome.send('disableEventLogRecordings');
+    }
+  }
+}
diff --git a/content/browser/webrtc/resources/peer_connection_update_table.js b/content/browser/webrtc/resources/peer_connection_update_table.js
index 3d9fb92..a84ca21 100644
--- a/content/browser/webrtc/resources/peer_connection_update_table.js
+++ b/content/browser/webrtc/resources/peer_connection_update_table.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 {$} from 'chrome://resources/js/util.m.js';
 
 /**
  * The data of a peer connection update.
@@ -9,41 +10,36 @@
  * @param {number} lid The id of the peer conneciton inside a renderer.
  * @param {string} type The type of the update.
  * @param {string} value The details of the update.
- * @constructor
  */
-var PeerConnectionUpdateEntry = function(pid, lid, type, value) {
-  /**
-   * @type {number}
-   */
-  this.pid = pid;
+class PeerConnectionUpdateEntry {
+  constructor(pid, lid, type, value) {
+    /**
+     * @type {number}
+     */
+    this.pid = pid;
 
-  /**
-   * @type {number}
-   */
-  this.lid = lid;
+    /**
+     * @type {number}
+     */
+    this.lid = lid;
 
-  /**
-   * @type {string}
-   */
-  this.type = type;
+    /**
+     * @type {string}
+     */
+    this.type = type;
 
-  /**
-   * @type {string}
-   */
-  this.value = value;
-};
-
+    /**
+     * @type {string}
+     */
+    this.value = value;
+  }
+}
 
 /**
  * Maintains the peer connection update log table.
  */
-var PeerConnectionUpdateTable = (function() {
-  'use strict';
-
-  /**
-   * @constructor
-   */
-  function PeerConnectionUpdateTable() {
+export class PeerConnectionUpdateTable {
+  constructor() {
     /**
      * @type {string}
      * @const
@@ -66,164 +62,183 @@
     this.UPDATE_LOG_TABLE_CLASS = 'update-log-table';
   }
 
-  PeerConnectionUpdateTable.prototype = {
-    /**
-     * Adds the update to the update table as a new row. The type of the update
-     * is set to the summary of the cell; clicking the cell will reveal or hide
-     * the details as the content of a TextArea element.
-     *
-     * @param {!Element} peerConnectionElement The root element.
-     * @param {!PeerConnectionUpdateEntry} update The update to add.
-     */
-    addPeerConnectionUpdate: function(peerConnectionElement, update) {
-      var tableElement = this.ensureUpdateContainer_(peerConnectionElement);
+  /**
+   * Adds the update to the update table as a new row. The type of the update
+   * is set to the summary of the cell; clicking the cell will reveal or hide
+   * the details as the content of a TextArea element.
+   *
+   * @param {!Element} peerConnectionElement The root element.
+   * @param {!PeerConnectionUpdateEntry} update The update to add.
+   */
+  addPeerConnectionUpdate(peerConnectionElement, update) {
+    var tableElement = this.ensureUpdateContainer_(peerConnectionElement);
 
-      var row = document.createElement('tr');
-      tableElement.firstChild.appendChild(row);
+    var row = document.createElement('tr');
+    tableElement.firstChild.appendChild(row);
 
-      var time = new Date(parseFloat(update.time));
-      const timeItem = document.createElement('td');
-      timeItem.textContent = time.toLocaleString();
-      row.appendChild(timeItem);
+    var time = new Date(parseFloat(update.time));
+    const timeItem = document.createElement('td');
+    timeItem.textContent = time.toLocaleString();
+    row.appendChild(timeItem);
 
-      // map internal event names to spec event names.
-      var type = {
-        onRenegotiationNeeded: 'negotiationneeded',
-        signalingStateChange: 'signalingstatechange',
-        iceGatheringStateChange: 'icegatheringstatechange',
-        legacyIceConnectionStateChange: 'iceconnectionstatechange (legacy)',
-        iceConnectionStateChange: 'iceconnectionstatechange',
-        connectionStateChange: 'connectionstatechange',
-        onIceCandidate: 'icecandidate',
-        stop: 'close'
-      }[update.type] ||
-          update.type;
+    // map internal event names to spec event names.
+    var type = {
+      onRenegotiationNeeded: 'negotiationneeded',
+      signalingStateChange: 'signalingstatechange',
+      iceGatheringStateChange: 'icegatheringstatechange',
+      legacyIceConnectionStateChange: 'iceconnectionstatechange (legacy)',
+      iceConnectionStateChange: 'iceconnectionstatechange',
+      connectionStateChange: 'connectionstatechange',
+      onIceCandidate: 'icecandidate',
+      stop: 'close'
+    }[update.type] ||
+        update.type;
 
-      if (update.value.length === 0) {
-        const typeItem = document.createElement('td');
-        typeItem.textContent = type;
-        row.appendChild(typeItem);
-        return;
-      }
-
-      if (update.type === 'onIceCandidate' ||
-          update.type === 'addIceCandidate') {
-        // extract ICE candidate type from the field following typ.
-        var candidateType = update.value.match(/(?: typ )(host|srflx|relay)/);
-        if (candidateType) {
-          type += ' (' + candidateType[1] + ')';
-        }
-      } else if (
-          update.type === 'createOfferOnSuccess' ||
-          update.type === 'createAnswerOnSuccess') {
-        this.setLastOfferAnswer_(tableElement, update);
-      } else if (update.type === 'setLocalDescription') {
-        if (update.value !== this.getLastOfferAnswer_(tableElement)) {
-          type += ' (munged)';
-        }
-      }
-      const summaryItem = $('summary-template').content.cloneNode(true);
-      summaryItem.querySelector('summary').textContent = type;
-      row.appendChild(summaryItem);
-
-      var valueContainer = document.createElement('pre');
-      var details = row.cells[1].childNodes[0];
-      details.appendChild(valueContainer);
-
-      // Highlight ICE failures and failure callbacks.
-      if ((update.type === 'iceConnectionStateChange' &&
-           update.value === 'ICEConnectionStateFailed') ||
-          update.type.indexOf('OnFailure') !== -1 ||
-          update.type === 'addIceCandidateFailed') {
-        valueContainer.parentElement.classList.add('update-log-failure');
-      }
-
-      var value = update.value;
-      // map internal names and values to names and events from the
-      // specification. This is a display change which shall not
-      // change the JSON dump.
-      if (update.type === 'iceConnectionStateChange') {
-        value = {
-          ICEConnectionStateNew: 'new',
-          ICEConnectionStateChecking: 'checking',
-          ICEConnectionStateConnected: 'connected',
-          ICEConnectionStateCompleted: 'completed',
-          ICEConnectionStateFailed: 'failed',
-          ICEConnectionStateDisconnected: 'disconnected',
-          ICEConnectionStateClosed: 'closed',
-        }[value] ||
-            value;
-      } else if (update.type === 'iceGatheringStateChange') {
-        value = {
-          ICEGatheringStateNew: 'new',
-          ICEGatheringStateGathering: 'gathering',
-          ICEGatheringStateComplete: 'complete',
-        }[value] ||
-            value;
-      } else if (update.type === 'signalingStateChange') {
-        value = {
-          SignalingStateStable: 'stable',
-          SignalingStateHaveLocalOffer: 'have-local-offer',
-          SignalingStateHaveRemoteOffer: 'have-remote-offer',
-          SignalingStateHaveLocalPrAnswer: 'have-local-pranswer',
-          SignalingStateHaveRemotePrAnswer: 'have-remote-pranswer',
-          SignalingStateClosed: 'closed',
-        }[value] ||
-            value;
-      }
-
-      valueContainer.textContent = value;
-    },
-
-    /**
-     * Makes sure the update log table of the peer connection is created.
-     *
-     * @param {!Element} peerConnectionElement The root element.
-     * @return {!Element} The log table element.
-     * @private
-     */
-    ensureUpdateContainer_: function(peerConnectionElement) {
-      var tableId = peerConnectionElement.id + this.UPDATE_LOG_ID_SUFFIX_;
-      var tableElement = $(tableId);
-      if (!tableElement) {
-        var tableContainer = document.createElement('div');
-        tableContainer.className = this.UPDATE_LOG_CONTAINER_CLASS_;
-        peerConnectionElement.appendChild(tableContainer);
-
-        tableElement = document.createElement('table');
-        tableElement.className = this.UPDATE_LOG_TABLE_CLASS;
-        tableElement.id = tableId;
-        tableElement.border = 1;
-        tableContainer.appendChild(tableElement);
-        tableElement.appendChild(
-            $('time-event-template').content.cloneNode(true));
-      }
-      return tableElement;
-    },
-
-    /**
-     * Store the last createOfferOnSuccess/createAnswerOnSuccess to compare to
-     * setLocalDescription and visualize SDP munging.
-     *
-     * @param {!Element} tableElement The peerconnection update element.
-     * @param {!PeerConnectionUpdateEntry} update The update to add.
-     * @private
-     */
-    setLastOfferAnswer_: function(tableElement, update) {
-      tableElement['data-lastofferanswer'] = update.value;
-    },
-
-    /**
-     * Retrieves the last createOfferOnSuccess/createAnswerOnSuccess to compare
-     * to setLocalDescription and visualize SDP munging.
-     *
-     * @param {!Element} tableElement The peerconnection update element.
-     * @private
-     */
-    getLastOfferAnswer_: function(tableElement) {
-      return tableElement['data-lastofferanswer'];
+    if (update.value.length === 0) {
+      const typeItem = document.createElement('td');
+      typeItem.textContent = type;
+      row.appendChild(typeItem);
+      return;
     }
-  };
 
-  return PeerConnectionUpdateTable;
-})();
+    if (update.type === 'onIceCandidate' || update.type === 'addIceCandidate') {
+      // extract ICE candidate type from the field following typ.
+      var candidateType = update.value.match(/(?: typ )(host|srflx|relay)/);
+      if (candidateType) {
+        type += ' (' + candidateType[1] + ')';
+      }
+    } else if (
+        update.type === 'createOfferOnSuccess' ||
+        update.type === 'createAnswerOnSuccess') {
+      this.setLastOfferAnswer_(tableElement, update);
+    } else if (update.type === 'setLocalDescription') {
+      if (update.value !== this.getLastOfferAnswer_(tableElement)) {
+        type += ' (munged)';
+      }
+    }
+    const summaryItem = $('summary-template').content.cloneNode(true);
+    const summary = summaryItem.querySelector('summary');
+    summary.textContent = type;
+    row.appendChild(summaryItem);
+
+    var valueContainer = document.createElement('pre');
+    var details = row.cells[1].childNodes[0];
+    details.appendChild(valueContainer);
+
+    // Highlight ICE failures and failure callbacks.
+    if ((update.type === 'iceConnectionStateChange' &&
+         update.value === 'ICEConnectionStateFailed') ||
+        update.type.indexOf('OnFailure') !== -1 ||
+        update.type === 'addIceCandidateFailed') {
+      valueContainer.parentElement.classList.add('update-log-failure');
+    }
+
+    var value = update.value;
+    // map internal names and values to names and events from the
+    // specification. This is a display change which shall not
+    // change the JSON dump.
+    if (update.type === 'iceConnectionStateChange') {
+      value = {
+        ICEConnectionStateNew: 'new',
+        ICEConnectionStateChecking: 'checking',
+        ICEConnectionStateConnected: 'connected',
+        ICEConnectionStateCompleted: 'completed',
+        ICEConnectionStateFailed: 'failed',
+        ICEConnectionStateDisconnected: 'disconnected',
+        ICEConnectionStateClosed: 'closed',
+      }[value] ||
+          value;
+    } else if (update.type === 'iceGatheringStateChange') {
+      value = {
+        ICEGatheringStateNew: 'new',
+        ICEGatheringStateGathering: 'gathering',
+        ICEGatheringStateComplete: 'complete',
+      }[value] ||
+          value;
+    } else if (update.type === 'signalingStateChange') {
+      value = {
+        SignalingStateStable: 'stable',
+        SignalingStateHaveLocalOffer: 'have-local-offer',
+        SignalingStateHaveRemoteOffer: 'have-remote-offer',
+        SignalingStateHaveLocalPrAnswer: 'have-local-pranswer',
+        SignalingStateHaveRemotePrAnswer: 'have-remote-pranswer',
+        SignalingStateClosed: 'closed',
+      }[value] ||
+          value;
+    }
+
+    // RTCSessionDescription is serialized as 'type: <type>, sdp:'
+    if (update.value.indexOf(', sdp:') !== -1) {
+      const [type, sdp] = update.value.substr(6).split(', sdp: ');
+      const sections = sdp.split('\nm=')
+        .map((part, index) => (index > 0 ?
+          'm=' + part : part).trim() + '\r\n');
+      summary.textContent +=
+        ' (type: "' + type + '", ' + sections.length + ' sections)';
+      sections.forEach(section => {
+        const lines = section.trim().split('\n');
+        const sectionDetails = document.createElement('details');
+        sectionDetails.open = true;
+        sectionDetails.textContent = lines.slice(1).join('\n');
+
+        const sectionSummary = document.createElement('summary');
+        sectionSummary.innerText =
+          lines[0].trim() + ' (' + (lines.length - 1) + ' more lines)';
+        sectionDetails.appendChild(sectionSummary);
+
+        valueContainer.appendChild(sectionDetails);
+      });
+    } else {
+      valueContainer.textContent = value;
+    }
+  }
+
+  /**
+   * Makes sure the update log table of the peer connection is created.
+   *
+   * @param {!Element} peerConnectionElement The root element.
+   * @return {!Element} The log table element.
+   * @private
+   */
+  ensureUpdateContainer_(peerConnectionElement) {
+    var tableId = peerConnectionElement.id + this.UPDATE_LOG_ID_SUFFIX_;
+    var tableElement = $(tableId);
+    if (!tableElement) {
+      var tableContainer = document.createElement('div');
+      tableContainer.className = this.UPDATE_LOG_CONTAINER_CLASS_;
+      peerConnectionElement.appendChild(tableContainer);
+
+      tableElement = document.createElement('table');
+      tableElement.className = this.UPDATE_LOG_TABLE_CLASS;
+      tableElement.id = tableId;
+      tableElement.border = 1;
+      tableContainer.appendChild(tableElement);
+      tableElement.appendChild(
+          $('time-event-template').content.cloneNode(true));
+    }
+    return tableElement;
+  }
+
+  /**
+   * Store the last createOfferOnSuccess/createAnswerOnSuccess to compare to
+   * setLocalDescription and visualize SDP munging.
+   *
+   * @param {!Element} tableElement The peerconnection update element.
+   * @param {!PeerConnectionUpdateEntry} update The update to add.
+   * @private
+   */
+  setLastOfferAnswer_(tableElement, update) {
+    tableElement['data-lastofferanswer'] = update.value;
+  }
+
+  /**
+   * Retrieves the last createOfferOnSuccess/createAnswerOnSuccess to compare
+   * to setLocalDescription and visualize SDP munging.
+   *
+   * @param {!Element} tableElement The peerconnection update element.
+   * @private
+   */
+  getLastOfferAnswer_(tableElement) {
+    return tableElement['data-lastofferanswer'];
+  }
+}
diff --git a/content/browser/webrtc/resources/resources.grd b/content/browser/webrtc/resources/resources.grd
deleted file mode 100644
index b1386c8..0000000
--- a/content/browser/webrtc/resources/resources.grd
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
-  <outputs>
-    <output filename="grit/webrtc_internals_resources.h" type="rc_header">
-      <emit emit_type='prepend'></emit>
-    </output>
-    <output filename="webrtc_internals_resources.pak" type="data_package" />
-  </outputs>
-  <translations />
-  <release seq="1">
-    <includes>
-      <include name="IDR_WEBRTC_INTERNALS_HTML"
-               file="webrtc_internals.html"
-               flattenhtml="true"
-               allowexternalscript="true"
-               type="BINDATA" />
-      <include name="IDR_WEBRTC_INTERNALS_JS"
-               file="webrtc_internals.js"
-               flattenhtml="true"
-               type="BINDATA" />
-    </includes>
-  </release>
-</grit>
diff --git a/content/browser/webrtc/resources/ssrc_info_manager.js b/content/browser/webrtc/resources/ssrc_info_manager.js
index 39a05d68..4888f40 100644
--- a/content/browser/webrtc/resources/ssrc_info_manager.js
+++ b/content/browser/webrtc/resources/ssrc_info_manager.js
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-
-
 /**
  * Get the ssrc if |report| is an ssrc report.
  *
@@ -13,7 +11,7 @@
  *     index entry is the value.
  * @return {?string} The ssrc.
  */
-function GetSsrcFromReport(report) {
+export function GetSsrcFromReport(report) {
   if (report.type !== 'ssrc') {
     console.warn('Trying to get ssrc from non-ssrc report.');
     return null;
@@ -39,13 +37,8 @@
 /**
  * SsrcInfoManager stores the ssrc stream info extracted from SDP.
  */
-var SsrcInfoManager = (function() {
-  'use strict';
-
-  /**
-   * @constructor
-   */
-  function SsrcInfoManager() {
+export class SsrcInfoManager {
+  constructor() {
     /**
      * Map from ssrc id to an object containing all the stream properties.
      * @type {!Object<!Object<string>>}
@@ -85,87 +78,83 @@
     this.SSRC_INFO_BLOCK_CLASS = 'ssrc-info-block';
   }
 
-  SsrcInfoManager.prototype = {
-    /**
-     * Extracts the stream information from |sdp| and saves it.
-     * For example:
-     *     a=ssrc:1234 msid:abcd
-     *     a=ssrc:1234 label:hello
-     *
-     * @param {string} sdp The SDP string.
-     */
-    addSsrcStreamInfo: function(sdp) {
-      var attributes = sdp.split(this.ATTRIBUTE_SEPARATOR_);
-      for (var i = 0; i < attributes.length; ++i) {
-        // Check if this is a ssrc attribute.
-        if (attributes[i].indexOf(this.SSRC_ATTRIBUTE_PREFIX_) !== 0) {
-          continue;
-        }
-
-        var nextFieldIndex = attributes[i].search(this.FIELD_SEPARATOR_REGEX_);
-
-        if (nextFieldIndex === -1) {
-          continue;
-        }
-
-        var ssrc = attributes[i].substring(
-            this.SSRC_ATTRIBUTE_PREFIX_.length, nextFieldIndex);
-        if (!this.streamInfoContainer_[ssrc]) {
-          this.streamInfoContainer_[ssrc] = {};
-        }
-
-        // Make |rest| starting at the next field.
-        var rest = attributes[i].substring(nextFieldIndex + 1);
-        var name, value;
-        while (rest.length > 0) {
-          nextFieldIndex = rest.search(this.FIELD_SEPARATOR_REGEX_);
-          if (nextFieldIndex === -1) {
-            nextFieldIndex = rest.length;
-          }
-
-          // The field name is the string before the colon.
-          name = rest.substring(0, rest.indexOf(':'));
-          // The field value is from after the colon to the next field.
-          value = rest.substring(rest.indexOf(':') + 1, nextFieldIndex);
-          this.streamInfoContainer_[ssrc][name] = value;
-
-          // Move |rest| to the start of the next field.
-          rest = rest.substring(nextFieldIndex + 1);
-        }
+  /**
+   * Extracts the stream information from |sdp| and saves it.
+   * For example:
+   *     a=ssrc:1234 msid:abcd
+   *     a=ssrc:1234 label:hello
+   *
+   * @param {string} sdp The SDP string.
+   */
+  addSsrcStreamInfo(sdp) {
+    var attributes = sdp.split(this.ATTRIBUTE_SEPARATOR_);
+    for (var i = 0; i < attributes.length; ++i) {
+      // Check if this is a ssrc attribute.
+      if (attributes[i].indexOf(this.SSRC_ATTRIBUTE_PREFIX_) !== 0) {
+        continue;
       }
-    },
 
-    /**
-     * @param {string} sdp The ssrc id.
-     * @return {!Object<string>} The object containing the ssrc infomation.
-     */
-    getStreamInfo: function(ssrc) {
-      return this.streamInfoContainer_[ssrc];
-    },
+      var nextFieldIndex = attributes[i].search(this.FIELD_SEPARATOR_REGEX_);
 
-    /**
-     * Populate the ssrc information into |parentElement|, each field as a
-     * DIV element.
-     *
-     * @param {!Element} parentElement The parent element for the ssrc info.
-     * @param {string} ssrc The ssrc id.
-     */
-    populateSsrcInfo: function(parentElement, ssrc) {
+      if (nextFieldIndex === -1) {
+        continue;
+      }
+
+      var ssrc = attributes[i].substring(
+          this.SSRC_ATTRIBUTE_PREFIX_.length, nextFieldIndex);
       if (!this.streamInfoContainer_[ssrc]) {
-        return;
+        this.streamInfoContainer_[ssrc] = {};
       }
 
-      parentElement.className = this.SSRC_INFO_BLOCK_CLASS;
+      // Make |rest| starting at the next field.
+      var rest = attributes[i].substring(nextFieldIndex + 1);
+      var name, value;
+      while (rest.length > 0) {
+        nextFieldIndex = rest.search(this.FIELD_SEPARATOR_REGEX_);
+        if (nextFieldIndex === -1) {
+          nextFieldIndex = rest.length;
+        }
 
-      var fieldElement;
-      for (var property in this.streamInfoContainer_[ssrc]) {
-        fieldElement = document.createElement('div');
-        parentElement.appendChild(fieldElement);
-        fieldElement.textContent =
-            property + ':' + this.streamInfoContainer_[ssrc][property];
+        // The field name is the string before the colon.
+        name = rest.substring(0, rest.indexOf(':'));
+        // The field value is from after the colon to the next field.
+        value = rest.substring(rest.indexOf(':') + 1, nextFieldIndex);
+        this.streamInfoContainer_[ssrc][name] = value;
+
+        // Move |rest| to the start of the next field.
+        rest = rest.substring(nextFieldIndex + 1);
       }
     }
-  };
+  }
 
-  return SsrcInfoManager;
-})();
+  /**
+   * @param {string} sdp The ssrc id.
+   * @return {!Object<string>} The object containing the ssrc information.
+   */
+  getStreamInfo(ssrc) {
+    return this.streamInfoContainer_[ssrc];
+  }
+
+  /**
+   * Populate the ssrc information into |parentElement|, each field as a
+   * DIV element.
+   *
+   * @param {!Element} parentElement The parent element for the ssrc info.
+   * @param {string} ssrc The ssrc id.
+   */
+  populateSsrcInfo(parentElement, ssrc) {
+    if (!this.streamInfoContainer_[ssrc]) {
+      return;
+    }
+
+    parentElement.className = this.SSRC_INFO_BLOCK_CLASS;
+
+    var fieldElement;
+    for (var property in this.streamInfoContainer_[ssrc]) {
+      fieldElement = document.createElement('div');
+      parentElement.appendChild(fieldElement);
+      fieldElement.textContent =
+          property + ':' + this.streamInfoContainer_[ssrc][property];
+    }
+  }
+}
diff --git a/content/browser/webrtc/resources/stats_graph_helper.js b/content/browser/webrtc/resources/stats_graph_helper.js
index cc222ab8..d2c87cc 100644
--- a/content/browser/webrtc/resources/stats_graph_helper.js
+++ b/content/browser/webrtc/resources/stats_graph_helper.js
@@ -11,7 +11,12 @@
 // Each group has an expand/collapse button and is collapsed initially.
 //
 
-// <include src="timeline_graph_view.js">
+import {$} from 'chrome://resources/js/util.m.js';
+
+import {TimelineDataSeries} from './data_series.js';
+import {peerConnectionDataStore} from './dump_creator.js';
+import {GetSsrcFromReport} from './ssrc_info_manager.js';
+import {TimelineGraphView} from './timeline_graph_view.js';
 
 var STATS_GRAPH_CONTAINER_HEADING_CLASS = 'stats-graph-container-heading';
 
@@ -97,7 +102,7 @@
   'googFingerprint': true,
 };
 
-function isStandardReportBlacklisted(report) {
+function isStandardReportBlocklisted(report) {
   // Codec stats reflect what has been negotiated. There are LOTS of them and
   // they don't change over time on their own.
   if (report.type === 'codec') {
@@ -132,7 +137,7 @@
   return undefined;
 }
 
-function isStandardStatBlacklisted(report, statName) {
+function isStandardStatBlocklisted(report, statName) {
   // The datachannelid is an identifier, but because it is a number it shows up
   // as a graph if we don't blacklist it.
   if (report.type === 'data-channel' && statName === 'datachannelid') {
@@ -146,6 +151,8 @@
 }
 
 var graphViews = {};
+// Export on |window| since tests access this directly from C++.
+window.graphViews = graphViews;
 let graphElementsByPeerConnectionId = new Map();
 
 // Returns number parsed from |value|, or NaN if the stats name is black-listed.
@@ -161,7 +168,8 @@
 
 // Adds the stats report |report| to the timeline graph for the given
 // |peerConnectionElement|.
-function drawSingleReport(peerConnectionElement, report, isLegacyReport) {
+export function drawSingleReport(
+    peerConnectionElement, report, isLegacyReport) {
   var reportType = report.type;
   var reportId = report.id;
   var stats = report.stats;
@@ -216,8 +224,8 @@
         [finalValue]);
 
     if (!isLegacyReport &&
-        (isStandardReportBlacklisted(report) ||
-         isStandardStatBlacklisted(report, rawLabel))) {
+        (isStandardReportBlocklisted(report) ||
+         isStandardStatBlocklisted(report, rawLabel))) {
       // We do not want to draw certain standard reports but still want to
       // record them in the data series.
       continue;
@@ -263,7 +271,7 @@
   }
 }
 
-function removeStatsReportGraphs(peerConnectionElement) {
+export function removeStatsReportGraphs(peerConnectionElement) {
   const graphElements =
       graphElementsByPeerConnectionId.get(peerConnectionElement.id);
   if (graphElements) {
diff --git a/content/browser/webrtc/resources/stats_rates_calculator.js b/content/browser/webrtc/resources/stats_rates_calculator.js
index 4bcee41..ccebdc2 100644
--- a/content/browser/webrtc/resources/stats_rates_calculator.js
+++ b/content/browser/webrtc/resources/stats_rates_calculator.js
@@ -83,7 +83,7 @@
 // associated with metrics from the original report. Convertible to and from the
 // "internal reports" format used by webrtc_internals.js to pass stats from C++
 // to JavaScript.
-class StatsReport {
+export class StatsReport {
   constructor() {
     // Represents an RTCStatsReport. It is a Map RTCStats.id -> RTCStats.
     // https://w3c.github.io/webrtc-pc/#dom-rtcstatsreport
@@ -394,7 +394,7 @@
 
 // Keeps track of previous and current stats report and calculates all
 // calculated metrics.
-class StatsRatesCalculator {
+export class StatsRatesCalculator {
   constructor() {
     this.previousReport = null;
     this.currentReport = null;
diff --git a/content/browser/webrtc/resources/stats_table.js b/content/browser/webrtc/resources/stats_table.js
index f567c8a0..af96e8e 100644
--- a/content/browser/webrtc/resources/stats_table.js
+++ b/content/browser/webrtc/resources/stats_table.js
@@ -2,19 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {$} from 'chrome://resources/js/util.m.js';
+
+import {GetSsrcFromReport, SsrcInfoManager} from './ssrc_info_manager.js';
 
 /**
  * Maintains the stats table.
  * @param {SsrcInfoManager} ssrcInfoManager The source of the ssrc info.
  */
-var StatsTable = (function(ssrcInfoManager) {
-  'use strict';
-
+export class StatsTable {
   /**
    * @param {SsrcInfoManager} ssrcInfoManager The source of the ssrc info.
-   * @constructor
    */
-  function StatsTable(ssrcInfoManager) {
+  constructor(ssrcInfoManager) {
     /**
      * @type {SsrcInfoManager}
      * @private
@@ -22,151 +22,147 @@
     this.ssrcInfoManager_ = ssrcInfoManager;
   }
 
-  StatsTable.prototype = {
-    /**
-     * Adds |report| to the stats table of |peerConnectionElement|.
-     *
-     * @param {!Element} peerConnectionElement The root element.
-     * @param {!Object} report The object containing stats, which is the object
-     *     containing timestamp and values, which is an array of strings, whose
-     *     even index entry is the name of the stat, and the odd index entry is
-     *     the value.
-     */
-    addStatsReport: function(peerConnectionElement, report) {
-      if (report.type === 'codec') {
-        return;
-      }
-      var statsTable = this.ensureStatsTable_(peerConnectionElement, report);
+  /**
+   * Adds |report| to the stats table of |peerConnectionElement|.
+   *
+   * @param {!Element} peerConnectionElement The root element.
+   * @param {!Object} report The object containing stats, which is the object
+   *     containing timestamp and values, which is an array of strings, whose
+   *     even index entry is the name of the stat, and the odd index entry is
+   *     the value.
+   */
+  addStatsReport(peerConnectionElement, report) {
+    if (report.type === 'codec') {
+      return;
+    }
+    var statsTable = this.ensureStatsTable_(peerConnectionElement, report);
 
-      if (report.stats) {
-        this.addStatsToTable_(
-            statsTable, report.stats.timestamp, report.stats.values);
-      }
-    },
+    if (report.stats) {
+      this.addStatsToTable_(
+          statsTable, report.stats.timestamp, report.stats.values);
+    }
+  }
 
-    clearStatsLists: function(peerConnectionElement) {
-      let containerId = peerConnectionElement.id + '-table-container';
-      let container = $(containerId);
-      if (container) {
-        peerConnectionElement.removeChild(container);
-        this.ensureStatsTableContainer_(peerConnectionElement);
-      }
-    },
+  clearStatsLists(peerConnectionElement) {
+    let containerId = peerConnectionElement.id + '-table-container';
+    let container = $(containerId);
+    if (container) {
+      peerConnectionElement.removeChild(container);
+      this.ensureStatsTableContainer_(peerConnectionElement);
+    }
+  }
 
-    /**
-     * Ensure the DIV container for the stats tables is created as a child of
-     * |peerConnectionElement|.
-     *
-     * @param {!Element} peerConnectionElement The root element.
-     * @return {!Element} The stats table container.
-     * @private
-     */
-    ensureStatsTableContainer_: function(peerConnectionElement) {
-      var containerId = peerConnectionElement.id + '-table-container';
-      var container = $(containerId);
-      if (!container) {
-        container = document.createElement('div');
-        container.id = containerId;
-        container.className = 'stats-table-container';
-        var head = document.createElement('div');
-        head.textContent = 'Stats Tables';
-        container.appendChild(head);
-        peerConnectionElement.appendChild(container);
-      }
-      return container;
-    },
+  /**
+   * Ensure the DIV container for the stats tables is created as a child of
+   * |peerConnectionElement|.
+   *
+   * @param {!Element} peerConnectionElement The root element.
+   * @return {!Element} The stats table container.
+   * @private
+   */
+  ensureStatsTableContainer_(peerConnectionElement) {
+    var containerId = peerConnectionElement.id + '-table-container';
+    var container = $(containerId);
+    if (!container) {
+      container = document.createElement('div');
+      container.id = containerId;
+      container.className = 'stats-table-container';
+      var head = document.createElement('div');
+      head.textContent = 'Stats Tables';
+      container.appendChild(head);
+      peerConnectionElement.appendChild(container);
+    }
+    return container;
+  }
 
-    /**
-     * Ensure the stats table for track specified by |report| of PeerConnection
-     * |peerConnectionElement| is created.
-     *
-     * @param {!Element} peerConnectionElement The root element.
-     * @param {!Object} report The object containing stats, which is the object
-     *     containing timestamp and values, which is an array of strings, whose
-     *     even index entry is the name of the stat, and the odd index entry is
-     *     the value.
-     * @return {!Element} The stats table element.
-     * @private
-     */
-    ensureStatsTable_: function(peerConnectionElement, report) {
-      var tableId = peerConnectionElement.id + '-table-' + report.id;
-      var table = $(tableId);
-      if (!table) {
-        var container = this.ensureStatsTableContainer_(peerConnectionElement);
-        var details = document.createElement('details');
-        container.appendChild(details);
+  /**
+   * Ensure the stats table for track specified by |report| of PeerConnection
+   * |peerConnectionElement| is created.
+   *
+   * @param {!Element} peerConnectionElement The root element.
+   * @param {!Object} report The object containing stats, which is the object
+   *     containing timestamp and values, which is an array of strings, whose
+   *     even index entry is the name of the stat, and the odd index entry is
+   *     the value.
+   * @return {!Element} The stats table element.
+   * @private
+   */
+  ensureStatsTable_(peerConnectionElement, report) {
+    var tableId = peerConnectionElement.id + '-table-' + report.id;
+    var table = $(tableId);
+    if (!table) {
+      var container = this.ensureStatsTableContainer_(peerConnectionElement);
+      var details = document.createElement('details');
+      container.appendChild(details);
 
-        var summary = document.createElement('summary');
-        summary.textContent = report.id + ' (' + report.type + ')';
-        details.appendChild(summary);
+      var summary = document.createElement('summary');
+      summary.textContent = report.id + ' (' + report.type + ')';
+      details.appendChild(summary);
 
-        table = document.createElement('table');
-        details.appendChild(table);
-        table.id = tableId;
-        table.border = 1;
+      table = document.createElement('table');
+      details.appendChild(table);
+      table.id = tableId;
+      table.border = 1;
 
-        table.appendChild($('trth-template').content.cloneNode(true));
-        table.rows[0].cells[0].textContent = 'Statistics ' + report.id;
-        if (report.type === 'ssrc') {
-          table.insertRow(1);
-          table.rows[1].appendChild(
-              $('td-colspan-template').content.cloneNode(true));
-          this.ssrcInfoManager_.populateSsrcInfo(
-              table.rows[1].cells[0], GetSsrcFromReport(report));
-        }
-      }
-      return table;
-    },
-
-    /**
-     * Update |statsTable| with |time| and |statsData|.
-     *
-     * @param {!Element} statsTable Which table to update.
-     * @param {number} time The number of miliseconds since epoch.
-     * @param {Array<string>} statsData An array of stats name and value pairs.
-     * @private
-     */
-    addStatsToTable_: function(statsTable, time, statsData) {
-      var date = new Date(time);
-      this.updateStatsTableRow_(statsTable, 'timestamp', date.toLocaleString());
-      for (var i = 0; i < statsData.length - 1; i = i + 2) {
-        this.updateStatsTableRow_(statsTable, statsData[i], statsData[i + 1]);
-      }
-    },
-
-    /**
-     * Update the value column of the stats row of |rowName| to |value|.
-     * A new row is created is this is the first report of this stats.
-     *
-     * @param {!Element} statsTable Which table to update.
-     * @param {string} rowName The name of the row to update.
-     * @param {string} value The new value to set.
-     * @private
-     */
-    updateStatsTableRow_: function(statsTable, rowName, value) {
-      var trId = statsTable.id + '-' + rowName;
-      var trElement = $(trId);
-      var activeConnectionClass = 'stats-table-active-connection';
-      if (!trElement) {
-        trElement = document.createElement('tr');
-        trElement.id = trId;
-        statsTable.firstChild.appendChild(trElement);
-        const item = $('td2-template').content.cloneNode(true);
-        item.querySelector('td').textContent = rowName;
-        trElement.appendChild(item);
-      }
-      trElement.cells[1].textContent = value;
-
-      // Highlights the table for the active connection.
-      if (rowName === 'googActiveConnection') {
-        if (value === true) {
-          statsTable.parentElement.classList.add(activeConnectionClass);
-        } else {
-          statsTable.parentElement.classList.remove(activeConnectionClass);
-        }
+      table.appendChild($('trth-template').content.cloneNode(true));
+      table.rows[0].cells[0].textContent = 'Statistics ' + report.id;
+      if (report.type === 'ssrc') {
+        table.insertRow(1);
+        table.rows[1].appendChild(
+            $('td-colspan-template').content.cloneNode(true));
+        this.ssrcInfoManager_.populateSsrcInfo(
+            table.rows[1].cells[0], GetSsrcFromReport(report));
       }
     }
-  };
+    return table;
+  }
 
-  return StatsTable;
-})();
+  /**
+   * Update |statsTable| with |time| and |statsData|.
+   *
+   * @param {!Element} statsTable Which table to update.
+   * @param {number} time The number of milliseconds since epoch.
+   * @param {Array<string>} statsData An array of stats name and value pairs.
+   * @private
+   */
+  addStatsToTable_(statsTable, time, statsData) {
+    var date = new Date(time);
+    this.updateStatsTableRow_(statsTable, 'timestamp', date.toLocaleString());
+    for (var i = 0; i < statsData.length - 1; i = i + 2) {
+      this.updateStatsTableRow_(statsTable, statsData[i], statsData[i + 1]);
+    }
+  }
+
+  /**
+   * Update the value column of the stats row of |rowName| to |value|.
+   * A new row is created is this is the first report of this stats.
+   *
+   * @param {!Element} statsTable Which table to update.
+   * @param {string} rowName The name of the row to update.
+   * @param {string} value The new value to set.
+   * @private
+   */
+  updateStatsTableRow_(statsTable, rowName, value) {
+    var trId = statsTable.id + '-' + rowName;
+    var trElement = $(trId);
+    var activeConnectionClass = 'stats-table-active-connection';
+    if (!trElement) {
+      trElement = document.createElement('tr');
+      trElement.id = trId;
+      statsTable.firstChild.appendChild(trElement);
+      const item = $('td2-template').content.cloneNode(true);
+      item.querySelector('td').textContent = rowName;
+      trElement.appendChild(item);
+    }
+    trElement.cells[1].textContent = value;
+
+    // Highlights the table for the active connection.
+    if (rowName === 'googActiveConnection') {
+      if (value === true) {
+        statsTable.parentElement.classList.add(activeConnectionClass);
+      } else {
+        statsTable.parentElement.classList.remove(activeConnectionClass);
+      }
+    }
+  }
+}
diff --git a/content/browser/webrtc/resources/tab_view.js b/content/browser/webrtc/resources/tab_view.js
index 9b20ae9..6edcd7d 100644
--- a/content/browser/webrtc/resources/tab_view.js
+++ b/content/browser/webrtc/resources/tab_view.js
@@ -2,19 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Creates a simple object containing the tab head and body elements.
+class TabDom {
+  constructor(h, b) {
+    this.head = h;
+    this.body = b;
+  }
+}
+
 /**
  * A TabView provides the ability to create tabs and switch between tabs. It's
  * responsible for creating the DOM and managing the visibility of each tab.
  * The first added tab is active by default and the others hidden.
  */
-var TabView = (function() {
-  'use strict';
-
+export class TabView {
   /**
-   * @constructor
    * @param {Element} root The root DOM element containing the tabs.
    */
-  function TabView(root) {
+  constructor(root) {
     this.root_ = root;
     this.ACTIVE_TAB_HEAD_CLASS_ = 'active-tab-head';
     this.ACTIVE_TAB_BODY_CLASS_ = 'active-tab-body';
@@ -33,89 +38,80 @@
     this.initializeHeadBar_();
   }
 
-  // Creates a simple object containing the tab head and body elements.
-  function TabDom(h, b) {
-    this.head = h;
-    this.body = b;
+  /**
+   * Adds a tab with the specified id and title.
+   * @param {string} id
+   * @param {string} title
+   * @return {!Element} The tab body element.
+   */
+  addTab(id, title) {
+    if (this.tabElements_[id]) {
+      throw 'Tab already exists: ' + id;
+    }
+
+    var head = document.createElement('span');
+    head.className = this.TAB_HEAD_CLASS_;
+    head.textContent = title;
+    head.title = title;
+    this.headBar_.appendChild(head);
+    head.addEventListener('click', this.switchTab_.bind(this, id));
+
+    var body = document.createElement('div');
+    body.className = this.TAB_BODY_CLASS_;
+    body.id = id;
+    this.root_.appendChild(body);
+
+    this.tabElements_[id] = new TabDom(head, body);
+
+    if (!this.activeTabId_) {
+      this.switchTab_(id);
+    }
+    return this.tabElements_[id].body;
   }
 
-  TabView.prototype = {
-    /**
-     * Adds a tab with the specified id and title.
-     * @param {string} id
-     * @param {string} title
-     * @return {!Element} The tab body element.
-     */
-    addTab: function(id, title) {
-      if (this.tabElements_[id]) {
-        throw 'Tab already exists: ' + id;
-      }
+  /** Removes the tab. @param {string} id */
+  removeTab(id) {
+    if (!this.tabElements_[id]) {
+      return;
+    }
+    this.tabElements_[id].head.parentNode.removeChild(
+        this.tabElements_[id].head);
+    this.tabElements_[id].body.parentNode.removeChild(
+        this.tabElements_[id].body);
 
-      var head = document.createElement('span');
-      head.className = this.TAB_HEAD_CLASS_;
-      head.textContent = title;
-      head.title = title;
-      this.headBar_.appendChild(head);
-      head.addEventListener('click', this.switchTab_.bind(this, id));
+    delete this.tabElements_[id];
+    if (this.activeTabId_ === id) {
+      this.switchTab_(Object.keys(this.tabElements_)[0]);
+    }
+  }
 
-      var body = document.createElement('div');
-      body.className = this.TAB_BODY_CLASS_;
-      body.id = id;
-      this.root_.appendChild(body);
+  /**
+   * Switches the specified tab into view.
+   *
+   * @param {string} activeId The id the of the tab that should be switched to
+   *     active state.
+   * @private
+   */
+  switchTab_(activeId) {
+    if (this.activeTabId_ && this.tabElements_[this.activeTabId_]) {
+      this.tabElements_[this.activeTabId_].body.classList.remove(
+          this.ACTIVE_TAB_BODY_CLASS_);
+      this.tabElements_[this.activeTabId_].head.classList.remove(
+          this.ACTIVE_TAB_HEAD_CLASS_);
+    }
+    this.activeTabId_ = activeId;
+    if (this.tabElements_[activeId]) {
+      this.tabElements_[activeId].body.classList.add(
+          this.ACTIVE_TAB_BODY_CLASS_);
+      this.tabElements_[activeId].head.classList.add(
+          this.ACTIVE_TAB_HEAD_CLASS_);
+    }
+  }
 
-      this.tabElements_[id] = new TabDom(head, body);
-
-      if (!this.activeTabId_) {
-        this.switchTab_(id);
-      }
-      return this.tabElements_[id].body;
-    },
-
-    /** Removes the tab. @param {string} id */
-    removeTab: function(id) {
-      if (!this.tabElements_[id]) {
-        return;
-      }
-      this.tabElements_[id].head.parentNode.removeChild(
-          this.tabElements_[id].head);
-      this.tabElements_[id].body.parentNode.removeChild(
-          this.tabElements_[id].body);
-
-      delete this.tabElements_[id];
-      if (this.activeTabId_ === id) {
-        this.switchTab_(Object.keys(this.tabElements_)[0]);
-      }
-    },
-
-    /**
-     * Switches the specified tab into view.
-     *
-     * @param {string} activeId The id the of the tab that should be switched to
-     *     active state.
-     * @private
-     */
-    switchTab_: function(activeId) {
-      if (this.activeTabId_ && this.tabElements_[this.activeTabId_]) {
-        this.tabElements_[this.activeTabId_].body.classList.remove(
-            this.ACTIVE_TAB_BODY_CLASS_);
-        this.tabElements_[this.activeTabId_].head.classList.remove(
-            this.ACTIVE_TAB_HEAD_CLASS_);
-      }
-      this.activeTabId_ = activeId;
-      if (this.tabElements_[activeId]) {
-        this.tabElements_[activeId].body.classList.add(
-            this.ACTIVE_TAB_BODY_CLASS_);
-        this.tabElements_[activeId].head.classList.add(
-            this.ACTIVE_TAB_HEAD_CLASS_);
-      }
-    },
-
-    /** Initializes the bar containing the tab heads. */
-    initializeHeadBar_: function() {
-      this.headBar_ = document.createElement('div');
-      this.root_.appendChild(this.headBar_);
-      this.headBar_.style.textAlign = 'center';
-    },
-  };
-  return TabView;
-})();
+  /** Initializes the bar containing the tab heads. */
+  initializeHeadBar_() {
+    this.headBar_ = document.createElement('div');
+    this.root_.appendChild(this.headBar_);
+    this.headBar_.style.textAlign = 'center';
+  }
+};
diff --git a/content/browser/webrtc/resources/timeline_graph_view.js b/content/browser/webrtc/resources/timeline_graph_view.js
index 936271bb..9a82774 100644
--- a/content/browser/webrtc/resources/timeline_graph_view.js
+++ b/content/browser/webrtc/resources/timeline_graph_view.js
@@ -2,37 +2,35 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {$} from 'chrome://resources/js/util.m.js';
+
+// Maximum number of labels placed vertically along the sides of the graph.
+var MAX_VERTICAL_LABELS = 6;
+
+// Vertical spacing between labels and between the graph and labels.
+var LABEL_VERTICAL_SPACING = 4;
+// Horizontal spacing between vertically placed labels and the edges of the
+// graph.
+var LABEL_HORIZONTAL_SPACING = 3;
+// Horizintal spacing between two horitonally placed labels along the bottom
+// of the graph.
+var LABEL_LABEL_HORIZONTAL_SPACING = 25;
+
+// Length of ticks, in pixels, next to y-axis labels.  The x-axis only has
+// one set of labels, so it can use lines instead.
+var Y_AXIS_TICK_LENGTH = 10;
+
+var GRID_COLOR = '#CCC';
+var TEXT_COLOR = '#000';
+var BACKGROUND_COLOR = '#FFF';
+
+var MAX_DECIMAL_PRECISION = 3;
+
 /**
  * A TimelineGraphView displays a timeline graph on a canvas element.
  */
-var TimelineGraphView = (function() {
-  'use strict';
-
-  // Maximum number of labels placed vertically along the sides of the graph.
-  var MAX_VERTICAL_LABELS = 6;
-
-  // Vertical spacing between labels and between the graph and labels.
-  var LABEL_VERTICAL_SPACING = 4;
-  // Horizontal spacing between vertically placed labels and the edges of the
-  // graph.
-  var LABEL_HORIZONTAL_SPACING = 3;
-  // Horizintal spacing between two horitonally placed labels along the bottom
-  // of the graph.
-  var LABEL_LABEL_HORIZONTAL_SPACING = 25;
-
-  // Length of ticks, in pixels, next to y-axis labels.  The x-axis only has
-  // one set of labels, so it can use lines instead.
-  var Y_AXIS_TICK_LENGTH = 10;
-
-  var GRID_COLOR = '#CCC';
-  var TEXT_COLOR = '#000';
-  var BACKGROUND_COLOR = '#FFF';
-
-  var MAX_DECIMAL_PRECISION = 3;
-  /**
-   * @constructor
-   */
-  function TimelineGraphView(divId, canvasId) {
+export class TimelineGraphView {
+  constructor(divId, canvasId) {
     this.scrollbar_ = {position_: 0, range_: 0};
 
     this.graphDiv_ = $(divId);
@@ -55,509 +53,497 @@
     this.updateScrollbarRange_(true);
   }
 
-  TimelineGraphView.prototype = {
-    setScale: function(scale) {
-      this.scale_ = scale;
-    },
+  setScale(scale) {
+    this.scale_ = scale;
+  }
 
-    // Returns the total length of the graph, in pixels.
-    getLength_: function() {
-      var timeRange = this.endTime_ - this.startTime_;
-      // Math.floor is used to ignore the last partial area, of length less
-      // than this.scale_.
-      return Math.floor(timeRange / this.scale_);
-    },
-
-    /**
-     * Returns true if the graph is scrolled all the way to the right.
-     */
-    graphScrolledToRightEdge_: function() {
-      return this.scrollbar_.position_ === this.scrollbar_.range_;
-    },
-
-    /**
-     * Update the range of the scrollbar.  If |resetPosition| is true, also
-     * sets the slider to point at the rightmost position and triggers a
-     * repaint.
-     */
-    updateScrollbarRange_: function(resetPosition) {
-      var scrollbarRange = this.getLength_() - this.canvas_.width;
-      if (scrollbarRange < 0) {
-        scrollbarRange = 0;
-      }
-
-      // If we've decreased the range to less than the current scroll position,
-      // we need to move the scroll position.
-      if (this.scrollbar_.position_ > scrollbarRange) {
-        resetPosition = true;
-      }
-
-      this.scrollbar_.range_ = scrollbarRange;
-      if (resetPosition) {
-        this.scrollbar_.position_ = scrollbarRange;
-        this.repaint();
-      }
-    },
-
-    /**
-     * Sets the date range displayed on the graph, switches to the default
-     * scale factor, and moves the scrollbar all the way to the right.
-     */
-    setDateRange: function(startDate, endDate) {
-      this.startTime_ = startDate.getTime();
-      this.endTime_ = endDate.getTime();
-
-      // Safety check.
-      if (this.endTime_ <= this.startTime_) {
-        this.startTime_ = this.endTime_ - 1;
-      }
-
-      this.updateScrollbarRange_(true);
-    },
-
-    /**
-     * Updates the end time at the right of the graph to be the current time.
-     * Specifically, updates the scrollbar's range, and if the scrollbar is
-     * all the way to the right, keeps it all the way to the right.  Otherwise,
-     * leaves the view as-is and doesn't redraw anything.
-     */
-    updateEndDate: function(opt_date) {
-      this.endTime_ = opt_date || (new Date()).getTime();
-      this.updateScrollbarRange_(this.graphScrolledToRightEdge_());
-    },
-
-    getStartDate: function() {
-      return new Date(this.startTime_);
-    },
-
-    /**
-     * Replaces the current TimelineDataSeries with |dataSeries|.
-     */
-    setDataSeries: function(dataSeries) {
-      // Simply recreates the Graph.
-      this.graph_ = new Graph();
-      for (var i = 0; i < dataSeries.length; ++i) {
-        this.graph_.addDataSeries(dataSeries[i]);
-      }
-      this.repaint();
-    },
-
-    /**
-     * Adds |dataSeries| to the current graph.
-     */
-    addDataSeries: function(dataSeries) {
-      if (!this.graph_) {
-        this.graph_ = new Graph();
-      }
-      this.graph_.addDataSeries(dataSeries);
-      this.repaint();
-    },
-
-    /**
-     * Draws the graph on |canvas_| when visible.
-     */
-    repaint: function() {
-      if (this.canvas_.offsetParent === null) {
-        return;  // do not repaint graphs that are not visible.
-      }
-
-      this.repaintTimerRunning_ = false;
-
-      var width = this.canvas_.width;
-      var height = this.canvas_.height;
-      var context = this.canvas_.getContext('2d');
-
-      // Clear the canvas.
-      context.fillStyle = BACKGROUND_COLOR;
-      context.fillRect(0, 0, width, height);
-
-      // Try to get font height in pixels.  Needed for layout.
-      var fontHeightString = context.font.match(/([0-9]+)px/)[1];
-      var fontHeight = parseInt(fontHeightString);
-
-      // Safety check, to avoid drawing anything too ugly.
-      if (fontHeightString.length === 0 || fontHeight <= 0 ||
-          fontHeight * 4 > height || width < 50) {
-        return;
-      }
-
-      // Save current transformation matrix so we can restore it later.
-      context.save();
-
-      // The center of an HTML canvas pixel is technically at (0.5, 0.5).  This
-      // makes near straight lines look bad, due to anti-aliasing.  This
-      // translation reduces the problem a little.
-      context.translate(0.5, 0.5);
-
-      // Figure out what time values to display.
-      var position = this.scrollbar_.position_;
-      // If the entire time range is being displayed, align the right edge of
-      // the graph to the end of the time range.
-      if (this.scrollbar_.range_ === 0) {
-        position = this.getLength_() - this.canvas_.width;
-      }
-      var visibleStartTime = this.startTime_ + position * this.scale_;
-
-      // Make space at the bottom of the graph for the time labels, and then
-      // draw the labels.
-      var textHeight = height;
-      height -= fontHeight + LABEL_VERTICAL_SPACING;
-      this.drawTimeLabels(context, width, height, textHeight, visibleStartTime);
-
-      // Draw outline of the main graph area.
-      context.strokeStyle = GRID_COLOR;
-      context.strokeRect(0, 0, width - 1, height - 1);
-
-      if (this.graph_) {
-        // Layout graph and have them draw their tick marks.
-        this.graph_.layout(
-            width, height, fontHeight, visibleStartTime, this.scale_);
-        this.graph_.drawTicks(context);
-
-        // Draw the lines of all graphs, and then draw their labels.
-        this.graph_.drawLines(context);
-        this.graph_.drawLabels(context);
-      }
-
-      // Restore original transformation matrix.
-      context.restore();
-    },
-
-    /**
-     * Draw time labels below the graph.  Takes in start time as an argument
-     * since it may not be |startTime_|, when we're displaying the entire
-     * time range.
-     */
-    drawTimeLabels: function(context, width, height, textHeight, startTime) {
-      // Draw the labels 1 minute apart.
-      var timeStep = 1000 * 60;
-
-      // Find the time for the first label.  This time is a perfect multiple of
-      // timeStep because of how UTC times work.
-      var time = Math.ceil(startTime / timeStep) * timeStep;
-
-      context.textBaseline = 'bottom';
-      context.textAlign = 'center';
-      context.fillStyle = TEXT_COLOR;
-      context.strokeStyle = GRID_COLOR;
-
-      // Draw labels and vertical grid lines.
-      while (true) {
-        var x = Math.round((time - startTime) / this.scale_);
-        if (x >= width) {
-          break;
-        }
-        var text = (new Date(time)).toLocaleTimeString();
-        context.fillText(text, x, textHeight);
-        context.beginPath();
-        context.lineTo(x, 0);
-        context.lineTo(x, height);
-        context.stroke();
-        time += timeStep;
-      }
-    },
-
-    getDataSeriesCount: function() {
-      if (this.graph_) {
-        return this.graph_.dataSeries_.length;
-      }
-      return 0;
-    },
-
-    hasDataSeries: function(dataSeries) {
-      if (this.graph_) {
-        return this.graph_.hasDataSeries(dataSeries);
-      }
-      return false;
-    },
-
-  };
+  // Returns the total length of the graph, in pixels.
+  getLength_() {
+    var timeRange = this.endTime_ - this.startTime_;
+    // Math.floor is used to ignore the last partial area, of length less
+    // than this.scale_.
+    return Math.floor(timeRange / this.scale_);
+  }
 
   /**
-   * A Graph is responsible for drawing all the TimelineDataSeries that have
-   * the same data type.  Graphs are responsible for scaling the values, laying
-   * out labels, and drawing both labels and lines for its data series.
+   * Returns true if the graph is scrolled all the way to the right.
    */
-  var Graph = (function() {
-    /**
-     * @constructor
-     */
-    function Graph() {
-      this.dataSeries_ = [];
+  graphScrolledToRightEdge_() {
+    return this.scrollbar_.position_ === this.scrollbar_.range_;
+  }
 
-      // Cached properties of the graph, set in layout.
-      this.width_ = 0;
-      this.height_ = 0;
-      this.fontHeight_ = 0;
-      this.startTime_ = 0;
-      this.scale_ = 0;
-
-      // The lowest/highest values adjusted by the vertical label step size
-      // in the displayed range of the graph. Used for scaling and setting
-      // labels.  Set in layoutLabels.
-      this.min_ = 0;
-      this.max_ = 0;
-
-      // Cached text of equally spaced labels.  Set in layoutLabels.
-      this.labels_ = [];
+  /**
+   * Update the range of the scrollbar.  If |resetPosition| is true, also
+   * sets the slider to point at the rightmost position and triggers a
+   * repaint.
+   */
+  updateScrollbarRange_(resetPosition) {
+    var scrollbarRange = this.getLength_() - this.canvas_.width;
+    if (scrollbarRange < 0) {
+      scrollbarRange = 0;
     }
 
-    /**
-     * A Label is the label at a particular position along the y-axis.
-     * @constructor
-     */
-    function Label(height, text) {
-      this.height = height;
-      this.text = text;
+    // If we've decreased the range to less than the current scroll position,
+    // we need to move the scroll position.
+    if (this.scrollbar_.position_ > scrollbarRange) {
+      resetPosition = true;
     }
 
-    Graph.prototype = {
-      addDataSeries: function(dataSeries) {
-        this.dataSeries_.push(dataSeries);
-      },
+    this.scrollbar_.range_ = scrollbarRange;
+    if (resetPosition) {
+      this.scrollbar_.position_ = scrollbarRange;
+      this.repaint();
+    }
+  }
 
-      hasDataSeries: function(dataSeries) {
-        for (var i = 0; i < this.dataSeries_.length; ++i) {
-          if (this.dataSeries_[i] === dataSeries) {
-            return true;
-          }
-        }
-        return false;
-      },
+  /**
+   * Sets the date range displayed on the graph, switches to the default
+   * scale factor, and moves the scrollbar all the way to the right.
+   */
+  setDateRange(startDate, endDate) {
+    this.startTime_ = startDate.getTime();
+    this.endTime_ = endDate.getTime();
 
-      /**
-       * Returns a list of all the values that should be displayed for a given
-       * data series, using the current graph layout.
-       */
-      getValues: function(dataSeries) {
-        if (!dataSeries.isVisible()) {
-          return null;
-        }
-        return dataSeries.getValues(this.startTime_, this.scale_, this.width_);
-      },
+    // Safety check.
+    if (this.endTime_ <= this.startTime_) {
+      this.startTime_ = this.endTime_ - 1;
+    }
 
-      /**
-       * Updates the graph's layout.  In particular, both the max value and
-       * label positions are updated.  Must be called before calling any of the
-       * drawing functions.
-       */
-      layout: function(width, height, fontHeight, startTime, scale) {
-        this.width_ = width;
-        this.height_ = height;
-        this.fontHeight_ = fontHeight;
-        this.startTime_ = startTime;
-        this.scale_ = scale;
+    this.updateScrollbarRange_(true);
+  }
 
-        // Find largest value.
-        var max = 0, min = 0;
-        for (var i = 0; i < this.dataSeries_.length; ++i) {
-          var values = this.getValues(this.dataSeries_[i]);
-          if (!values) {
-            continue;
-          }
-          for (var j = 0; j < values.length; ++j) {
-            if (values[j] > max) {
-              max = values[j];
-            } else if (values[j] < min) {
-              min = values[j];
-            }
-          }
-        }
+  /**
+   * Updates the end time at the right of the graph to be the current time.
+   * Specifically, updates the scrollbar's range, and if the scrollbar is
+   * all the way to the right, keeps it all the way to the right.  Otherwise,
+   * leaves the view as-is and doesn't redraw anything.
+   */
+  updateEndDate(opt_date) {
+    this.endTime_ = opt_date || (new Date()).getTime();
+    this.updateScrollbarRange_(this.graphScrolledToRightEdge_());
+  }
 
-        this.layoutLabels_(min, max);
-      },
+  getStartDate() {
+    return new Date(this.startTime_);
+  }
 
-      /**
-       * Lays out labels and sets |max_|/|min_|, taking the time units into
-       * consideration.  |maxValue| is the actual maximum value, and
-       * |max_| will be set to the value of the largest label, which
-       * will be at least |maxValue|. Similar for |min_|.
-       */
-      layoutLabels_: function(minValue, maxValue) {
-        if (maxValue - minValue < 1024) {
-          this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);
-          return;
-        }
+  /**
+   * Replaces the current TimelineDataSeries with |dataSeries|.
+   */
+  setDataSeries(dataSeries) {
+    // Simply recreates the Graph.
+    this.graph_ = new Graph();
+    for (var i = 0; i < dataSeries.length; ++i) {
+      this.graph_.addDataSeries(dataSeries[i]);
+    }
+    this.repaint();
+  }
 
-        // Find appropriate units to use.
-        var units = ['', 'k', 'M', 'G', 'T', 'P'];
-        // Units to use for labels.  0 is '1', 1 is K, etc.
-        // We start with 1, and work our way up.
-        var unit = 1;
-        minValue /= 1024;
-        maxValue /= 1024;
-        while (units[unit + 1] && maxValue - minValue >= 1024) {
-          minValue /= 1024;
-          maxValue /= 1024;
-          ++unit;
-        }
+  /**
+   * Adds |dataSeries| to the current graph.
+   */
+  addDataSeries(dataSeries) {
+    if (!this.graph_) {
+      this.graph_ = new Graph();
+    }
+    this.graph_.addDataSeries(dataSeries);
+    this.repaint();
+  }
 
-        // Calculate labels.
-        this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);
+  /**
+   * Draws the graph on |canvas_| when visible.
+   */
+  repaint() {
+    if (this.canvas_.offsetParent === null) {
+      return;  // do not repaint graphs that are not visible.
+    }
 
-        // Append units to labels.
-        for (var i = 0; i < this.labels_.length; ++i) {
-          this.labels_[i] += ' ' + units[unit];
-        }
+    this.repaintTimerRunning_ = false;
 
-        // Convert |min_|/|max_| back to unit '1'.
-        this.min_ *= Math.pow(1024, unit);
-        this.max_ *= Math.pow(1024, unit);
-      },
+    var width = this.canvas_.width;
+    var height = this.canvas_.height;
+    var context = this.canvas_.getContext('2d');
 
-      /**
-       * Same as layoutLabels_, but ignores units.  |maxDecimalDigits| is the
-       * maximum number of decimal digits allowed.  The minimum allowed
-       * difference between two adjacent labels is 10^-|maxDecimalDigits|.
-       */
-      layoutLabelsBasic_: function(minValue, maxValue, maxDecimalDigits) {
-        this.labels_ = [];
-        var range = maxValue - minValue;
-        // No labels if the range is 0.
-        if (range === 0) {
-          this.min_ = this.max_ = maxValue;
-          return;
-        }
+    // Clear the canvas.
+    context.fillStyle = BACKGROUND_COLOR;
+    context.fillRect(0, 0, width, height);
 
-        // The maximum number of equally spaced labels allowed.  |fontHeight_|
-        // is doubled because the top two labels are both drawn in the same
-        // gap.
-        var minLabelSpacing = 2 * this.fontHeight_ + LABEL_VERTICAL_SPACING;
+    // Try to get font height in pixels.  Needed for layout.
+    var fontHeightString = context.font.match(/([0-9]+)px/)[1];
+    var fontHeight = parseInt(fontHeightString);
 
-        // The + 1 is for the top label.
-        var maxLabels = 1 + this.height_ / minLabelSpacing;
-        if (maxLabels < 2) {
-          maxLabels = 2;
-        } else if (maxLabels > MAX_VERTICAL_LABELS) {
-          maxLabels = MAX_VERTICAL_LABELS;
-        }
+    // Safety check, to avoid drawing anything too ugly.
+    if (fontHeightString.length === 0 || fontHeight <= 0 ||
+        fontHeight * 4 > height || width < 50) {
+      return;
+    }
 
-        // Initial try for step size between conecutive labels.
-        var stepSize = Math.pow(10, -maxDecimalDigits);
-        // Number of digits to the right of the decimal of |stepSize|.
-        // Used for formating label strings.
-        var stepSizeDecimalDigits = maxDecimalDigits;
+    // Save current transformation matrix so we can restore it later.
+    context.save();
 
-        // Pick a reasonable step size.
-        while (true) {
-          // If we use a step size of |stepSize| between labels, we'll need:
-          //
-          // Math.ceil(range / stepSize) + 1
-          //
-          // labels.  The + 1 is because we need labels at both at 0 and at
-          // the top of the graph.
+    // The center of an HTML canvas pixel is technically at (0.5, 0.5).  This
+    // makes near straight lines look bad, due to anti-aliasing.  This
+    // translation reduces the problem a little.
+    context.translate(0.5, 0.5);
 
-          // Check if we can use steps of size |stepSize|.
-          if (Math.ceil(range / stepSize) + 1 <= maxLabels) {
-            break;
-          }
-          // Check |stepSize| * 2.
-          if (Math.ceil(range / (stepSize * 2)) + 1 <= maxLabels) {
-            stepSize *= 2;
-            break;
-          }
-          // Check |stepSize| * 5.
-          if (Math.ceil(range / (stepSize * 5)) + 1 <= maxLabels) {
-            stepSize *= 5;
-            break;
-          }
-          stepSize *= 10;
-          if (stepSizeDecimalDigits > 0) {
-            --stepSizeDecimalDigits;
-          }
-        }
+    // Figure out what time values to display.
+    var position = this.scrollbar_.position_;
+    // If the entire time range is being displayed, align the right edge of
+    // the graph to the end of the time range.
+    if (this.scrollbar_.range_ === 0) {
+      position = this.getLength_() - this.canvas_.width;
+    }
+    var visibleStartTime = this.startTime_ + position * this.scale_;
 
-        // Set the min/max so it's an exact multiple of the chosen step size.
-        this.max_ = Math.ceil(maxValue / stepSize) * stepSize;
-        this.min_ = Math.floor(minValue / stepSize) * stepSize;
+    // Make space at the bottom of the graph for the time labels, and then
+    // draw the labels.
+    var textHeight = height;
+    height -= fontHeight + LABEL_VERTICAL_SPACING;
+    this.drawTimeLabels(context, width, height, textHeight, visibleStartTime);
 
-        // Create labels.
-        for (var label = this.max_; label >= this.min_; label -= stepSize) {
-          this.labels_.push(label.toFixed(stepSizeDecimalDigits));
-        }
-      },
+    // Draw outline of the main graph area.
+    context.strokeStyle = GRID_COLOR;
+    context.strokeRect(0, 0, width - 1, height - 1);
 
-      /**
-       * Draws tick marks for each of the labels in |labels_|.
-       */
-      drawTicks: function(context) {
-        var x1;
-        var x2;
-        x1 = this.width_ - 1;
-        x2 = this.width_ - 1 - Y_AXIS_TICK_LENGTH;
+    if (this.graph_) {
+      // Layout graph and have them draw their tick marks.
+      this.graph_.layout(
+          width, height, fontHeight, visibleStartTime, this.scale_);
+      this.graph_.drawTicks(context);
 
-        context.fillStyle = GRID_COLOR;
-        context.beginPath();
-        for (var i = 1; i < this.labels_.length - 1; ++i) {
-          // The rounding is needed to avoid ugly 2-pixel wide anti-aliased
-          // lines.
-          var y = Math.round(this.height_ * i / (this.labels_.length - 1));
-          context.moveTo(x1, y);
-          context.lineTo(x2, y);
-        }
-        context.stroke();
-      },
+      // Draw the lines of all graphs, and then draw their labels.
+      this.graph_.drawLines(context);
+      this.graph_.drawLabels(context);
+    }
 
-      /**
-       * Draws a graph line for each of the data series.
-       */
-      drawLines: function(context) {
-        // Factor by which to scale all values to convert them to a number from
-        // 0 to height - 1.
-        var scale = 0;
-        var bottom = this.height_ - 1;
-        if (this.max_) {
-          scale = bottom / (this.max_ - this.min_);
-        }
+    // Restore original transformation matrix.
+    context.restore();
+  }
 
-        // Draw in reverse order, so earlier data series are drawn on top of
-        // subsequent ones.
-        for (var i = this.dataSeries_.length - 1; i >= 0; --i) {
-          var values = this.getValues(this.dataSeries_[i]);
-          if (!values) {
-            continue;
-          }
-          context.strokeStyle = this.dataSeries_[i].getColor();
-          context.beginPath();
-          for (var x = 0; x < values.length; ++x) {
-            // The rounding is needed to avoid ugly 2-pixel wide anti-aliased
-            // horizontal lines.
-            context.lineTo(
-                x, bottom - Math.round((values[x] - this.min_) * scale));
-          }
-          context.stroke();
-        }
-      },
+  /**
+   * Draw time labels below the graph.  Takes in start time as an argument
+   * since it may not be |startTime_|, when we're displaying the entire
+   * time range.
+   */
+  drawTimeLabels(context, width, height, textHeight, startTime) {
+    // Draw the labels 1 minute apart.
+    var timeStep = 1000 * 60;
 
-      /**
-       * Draw labels in |labels_|.
-       */
-      drawLabels: function(context) {
-        if (this.labels_.length === 0) {
-          return;
-        }
-        var x = this.width_ - LABEL_HORIZONTAL_SPACING;
+    // Find the time for the first label.  This time is a perfect multiple of
+    // timeStep because of how UTC times work.
+    var time = Math.ceil(startTime / timeStep) * timeStep;
 
-        // Set up the context.
-        context.fillStyle = TEXT_COLOR;
-        context.textAlign = 'right';
+    context.textBaseline = 'bottom';
+    context.textAlign = 'center';
+    context.fillStyle = TEXT_COLOR;
+    context.strokeStyle = GRID_COLOR;
 
-        // Draw top label, which is the only one that appears below its tick
-        // mark.
-        context.textBaseline = 'top';
-        context.fillText(this.labels_[0], x, 0);
+    // Draw labels and vertical grid lines.
+    while (true) {
+      var x = Math.round((time - startTime) / this.scale_);
+      if (x >= width) {
+        break;
+      }
+      var text = (new Date(time)).toLocaleTimeString();
+      context.fillText(text, x, textHeight);
+      context.beginPath();
+      context.lineTo(x, 0);
+      context.lineTo(x, height);
+      context.stroke();
+      time += timeStep;
+    }
+  }
 
-        // Draw all the other labels.
-        context.textBaseline = 'bottom';
-        var step = (this.height_ - 1) / (this.labels_.length - 1);
-        for (var i = 1; i < this.labels_.length; ++i) {
-          context.fillText(this.labels_[i], x, step * i);
+  getDataSeriesCount() {
+    if (this.graph_) {
+      return this.graph_.dataSeries_.length;
+    }
+    return 0;
+  }
+
+  hasDataSeries(dataSeries) {
+    if (this.graph_) {
+      return this.graph_.hasDataSeries(dataSeries);
+    }
+    return false;
+  }
+}
+
+/**
+ * A Label is the label at a particular position along the y-axis.
+ */
+class Label {
+  constructor(height, text) {
+    this.height = height;
+    this.text = text;
+  }
+}
+
+/**
+ * A Graph is responsible for drawing all the TimelineDataSeries that have
+ * the same data type.  Graphs are responsible for scaling the values, laying
+ * out labels, and drawing both labels and lines for its data series.
+ */
+class Graph {
+  constructor() {
+    this.dataSeries_ = [];
+
+    // Cached properties of the graph, set in layout.
+    this.width_ = 0;
+    this.height_ = 0;
+    this.fontHeight_ = 0;
+    this.startTime_ = 0;
+    this.scale_ = 0;
+
+    // The lowest/highest values adjusted by the vertical label step size
+    // in the displayed range of the graph. Used for scaling and setting
+    // labels.  Set in layoutLabels.
+    this.min_ = 0;
+    this.max_ = 0;
+
+    // Cached text of equally spaced labels.  Set in layoutLabels.
+    this.labels_ = [];
+  }
+
+  addDataSeries(dataSeries) {
+    this.dataSeries_.push(dataSeries);
+  }
+
+  hasDataSeries(dataSeries) {
+    for (var i = 0; i < this.dataSeries_.length; ++i) {
+      if (this.dataSeries_[i] === dataSeries) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns a list of all the values that should be displayed for a given
+   * data series, using the current graph layout.
+   */
+  getValues(dataSeries) {
+    if (!dataSeries.isVisible()) {
+      return null;
+    }
+    return dataSeries.getValues(this.startTime_, this.scale_, this.width_);
+  }
+
+  /**
+   * Updates the graph's layout.  In particular, both the max value and
+   * label positions are updated.  Must be called before calling any of the
+   * drawing functions.
+   */
+  layout(width, height, fontHeight, startTime, scale) {
+    this.width_ = width;
+    this.height_ = height;
+    this.fontHeight_ = fontHeight;
+    this.startTime_ = startTime;
+    this.scale_ = scale;
+
+    // Find largest value.
+    var max = 0, min = 0;
+    for (var i = 0; i < this.dataSeries_.length; ++i) {
+      var values = this.getValues(this.dataSeries_[i]);
+      if (!values) {
+        continue;
+      }
+      for (var j = 0; j < values.length; ++j) {
+        if (values[j] > max) {
+          max = values[j];
+        } else if (values[j] < min) {
+          min = values[j];
         }
       }
-    };
+    }
 
-    return Graph;
-  })();
+    this.layoutLabels_(min, max);
+  }
 
-  return TimelineGraphView;
-})();
+  /**
+   * Lays out labels and sets |max_|/|min_|, taking the time units into
+   * consideration.  |maxValue| is the actual maximum value, and
+   * |max_| will be set to the value of the largest label, which
+   * will be at least |maxValue|. Similar for |min_|.
+   */
+  layoutLabels_(minValue, maxValue) {
+    if (maxValue - minValue < 1024) {
+      this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);
+      return;
+    }
+
+    // Find appropriate units to use.
+    var units = ['', 'k', 'M', 'G', 'T', 'P'];
+    // Units to use for labels.  0 is '1', 1 is K, etc.
+    // We start with 1, and work our way up.
+    var unit = 1;
+    minValue /= 1024;
+    maxValue /= 1024;
+    while (units[unit + 1] && maxValue - minValue >= 1024) {
+      minValue /= 1024;
+      maxValue /= 1024;
+      ++unit;
+    }
+
+    // Calculate labels.
+    this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);
+
+    // Append units to labels.
+    for (var i = 0; i < this.labels_.length; ++i) {
+      this.labels_[i] += ' ' + units[unit];
+    }
+
+    // Convert |min_|/|max_| back to unit '1'.
+    this.min_ *= Math.pow(1024, unit);
+    this.max_ *= Math.pow(1024, unit);
+  }
+
+  /**
+   * Same as layoutLabels_, but ignores units.  |maxDecimalDigits| is the
+   * maximum number of decimal digits allowed.  The minimum allowed
+   * difference between two adjacent labels is 10^-|maxDecimalDigits|.
+   */
+  layoutLabelsBasic_(minValue, maxValue, maxDecimalDigits) {
+    this.labels_ = [];
+    var range = maxValue - minValue;
+    // No labels if the range is 0.
+    if (range === 0) {
+      this.min_ = this.max_ = maxValue;
+      return;
+    }
+
+    // The maximum number of equally spaced labels allowed.  |fontHeight_|
+    // is doubled because the top two labels are both drawn in the same
+    // gap.
+    var minLabelSpacing = 2 * this.fontHeight_ + LABEL_VERTICAL_SPACING;
+
+    // The + 1 is for the top label.
+    var maxLabels = 1 + this.height_ / minLabelSpacing;
+    if (maxLabels < 2) {
+      maxLabels = 2;
+    } else if (maxLabels > MAX_VERTICAL_LABELS) {
+      maxLabels = MAX_VERTICAL_LABELS;
+    }
+
+    // Initial try for step size between consecutive labels.
+    var stepSize = Math.pow(10, -maxDecimalDigits);
+    // Number of digits to the right of the decimal of |stepSize|.
+    // Used for formatting label strings.
+    var stepSizeDecimalDigits = maxDecimalDigits;
+
+    // Pick a reasonable step size.
+    while (true) {
+      // If we use a step size of |stepSize| between labels, we'll need:
+      //
+      // Math.ceil(range / stepSize) + 1
+      //
+      // labels.  The + 1 is because we need labels at both at 0 and at
+      // the top of the graph.
+
+      // Check if we can use steps of size |stepSize|.
+      if (Math.ceil(range / stepSize) + 1 <= maxLabels) {
+        break;
+      }
+      // Check |stepSize| * 2.
+      if (Math.ceil(range / (stepSize * 2)) + 1 <= maxLabels) {
+        stepSize *= 2;
+        break;
+      }
+      // Check |stepSize| * 5.
+      if (Math.ceil(range / (stepSize * 5)) + 1 <= maxLabels) {
+        stepSize *= 5;
+        break;
+      }
+      stepSize *= 10;
+      if (stepSizeDecimalDigits > 0) {
+        --stepSizeDecimalDigits;
+      }
+    }
+
+    // Set the min/max so it's an exact multiple of the chosen step size.
+    this.max_ = Math.ceil(maxValue / stepSize) * stepSize;
+    this.min_ = Math.floor(minValue / stepSize) * stepSize;
+
+    // Create labels.
+    for (var label = this.max_; label >= this.min_; label -= stepSize) {
+      this.labels_.push(label.toFixed(stepSizeDecimalDigits));
+    }
+  }
+
+  /**
+   * Draws tick marks for each of the labels in |labels_|.
+   */
+  drawTicks(context) {
+    var x1;
+    var x2;
+    x1 = this.width_ - 1;
+    x2 = this.width_ - 1 - Y_AXIS_TICK_LENGTH;
+
+    context.fillStyle = GRID_COLOR;
+    context.beginPath();
+    for (var i = 1; i < this.labels_.length - 1; ++i) {
+      // The rounding is needed to avoid ugly 2-pixel wide anti-aliased
+      // lines.
+      var y = Math.round(this.height_ * i / (this.labels_.length - 1));
+      context.moveTo(x1, y);
+      context.lineTo(x2, y);
+    }
+    context.stroke();
+  }
+
+  /**
+   * Draws a graph line for each of the data series.
+   */
+  drawLines(context) {
+    // Factor by which to scale all values to convert them to a number from
+    // 0 to height - 1.
+    var scale = 0;
+    var bottom = this.height_ - 1;
+    if (this.max_) {
+      scale = bottom / (this.max_ - this.min_);
+    }
+
+    // Draw in reverse order, so earlier data series are drawn on top of
+    // subsequent ones.
+    for (var i = this.dataSeries_.length - 1; i >= 0; --i) {
+      var values = this.getValues(this.dataSeries_[i]);
+      if (!values) {
+        continue;
+      }
+      context.strokeStyle = this.dataSeries_[i].getColor();
+      context.beginPath();
+      for (var x = 0; x < values.length; ++x) {
+        // The rounding is needed to avoid ugly 2-pixel wide anti-aliased
+        // horizontal lines.
+        context.lineTo(x, bottom - Math.round((values[x] - this.min_) * scale));
+      }
+      context.stroke();
+    }
+  }
+
+  /**
+   * Draw labels in |labels_|.
+   */
+  drawLabels(context) {
+    if (this.labels_.length === 0) {
+      return;
+    }
+    var x = this.width_ - LABEL_HORIZONTAL_SPACING;
+
+    // Set up the context.
+    context.fillStyle = TEXT_COLOR;
+    context.textAlign = 'right';
+
+    // Draw top label, which is the only one that appears below its tick
+    // mark.
+    context.textBaseline = 'top';
+    context.fillText(this.labels_[0], x, 0);
+
+    // Draw all the other labels.
+    context.textBaseline = 'bottom';
+    var step = (this.height_ - 1) / (this.labels_.length - 1);
+    for (var i = 1; i < this.labels_.length; ++i) {
+      context.fillText(this.labels_[i], x, step * i);
+    }
+  }
+}
diff --git a/content/browser/webrtc/resources/webrtc_internals.css b/content/browser/webrtc/resources/webrtc_internals.css
index e6c1a33..934a8ff 100644
--- a/content/browser/webrtc/resources/webrtc_internals.css
+++ b/content/browser/webrtc/resources/webrtc_internals.css
@@ -128,3 +128,7 @@
 .audio-diagnostic-dumps-info {
   max-width: 60em;
 }
+
+details[open] details summary {
+    background-color: rgb(220, 220, 220);
+}
diff --git a/content/browser/webrtc/resources/webrtc_internals.html b/content/browser/webrtc/resources/webrtc_internals.html
index 77ca44e..6235606 100644
--- a/content/browser/webrtc/resources/webrtc_internals.html
+++ b/content/browser/webrtc/resources/webrtc_internals.html
@@ -4,9 +4,7 @@
     <meta charset="utf-8">
     <title>WebRTC Internals</title>
     <link rel="stylesheet" href="webrtc_internals.css">
-    <script src="chrome://resources/js/assert.js"></script>
-    <script src="chrome://resources/js/util.js"></script>
-    <script src="webrtc_internals.js"></script>
+    <script type="module" src="webrtc_internals.js"></script>
   </head>
   <body>
     <p id="content-root"></p>
diff --git a/content/browser/webrtc/resources/webrtc_internals.js b/content/browser/webrtc/resources/webrtc_internals.js
index e7a7002..29db9906 100644
--- a/content/browser/webrtc/resources/webrtc_internals.js
+++ b/content/browser/webrtc/resources/webrtc_internals.js
@@ -2,6 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {addWebUIListener, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+import {$} from 'chrome://resources/js/util.m.js';
+
+import {MAX_STATS_DATA_POINT_BUFFER_SIZE} from './data_series.js';
+import {DumpCreator, peerConnectionDataStore, userMediaRequests} from './dump_creator.js';
+import {PeerConnectionUpdateTable} from './peer_connection_update_table.js';
+import {SsrcInfoManager} from './ssrc_info_manager.js';
+import {drawSingleReport, removeStatsReportGraphs} from './stats_graph_helper.js';
+import {StatsRatesCalculator, StatsReport} from './stats_rates_calculator.js';
+import {StatsTable} from './stats_table.js';
+import {TabView} from './tab_view.js';
+
 var USER_MEDIA_TAB_ID = 'user-media-tab-id';
 
 const OPTION_GETSTATS_STANDARD = 'Standardized (promise-based) getStats() API';
@@ -14,18 +26,20 @@
 var peerConnectionUpdateTable = null;
 var statsTable = null;
 var dumpCreator = null;
-/** A map from peer connection id to the PeerConnectionRecord. */
-var peerConnectionDataStore = {};
-/** A list of getUserMedia requests. */
-var userMediaRequests = [];
+
+// Exporting these on window since they are directly accessed by tests.
+window.setCurrentGetStatsMethod = function(method) {
+  currentGetStatsMethod = method;
+};
+window.OPTION_GETSTATS_LEGACY = OPTION_GETSTATS_LEGACY;
 
 /** Maps from id (see getPeerConnectionId) to StatsRatesCalculator. */
-statsRatesCalculatorById = new Map();
+const statsRatesCalculatorById = new Map();
 
 /** A simple class to store the updates and stats data for a peer connection. */
-var PeerConnectionRecord = (function() {
   /** @constructor */
-  function PeerConnectionRecord() {
+class PeerConnectionRecord {
+  constructor() {
     /** @private */
     this.record_ = {
       constraints: {},
@@ -36,84 +50,94 @@
     };
   }
 
-  PeerConnectionRecord.prototype = {
-    /** @override */
-    toJSON: function() {
-      return this.record_;
-    },
+  /** @override */
+  toJSON() {
+    return this.record_;
+  }
 
-    /**
-     * Adds the initilization info of the peer connection.
-     * @param {string} url The URL of the web page owning the peer connection.
-     * @param {Array} rtcConfiguration
-     * @param {!Object} constraints Media constraints.
-     */
-    initialize: function(url, rtcConfiguration, constraints) {
-      this.record_.url = url;
-      this.record_.rtcConfiguration = rtcConfiguration;
-      this.record_.constraints = constraints;
-    },
+  /**
+   * Adds the initialization info of the peer connection.
+   * @param {string} url The URL of the web page owning the peer connection.
+   * @param {Array} rtcConfiguration
+   * @param {!Object} constraints Media constraints.
+   */
+  initialize(url, rtcConfiguration, constraints) {
+    this.record_.url = url;
+    this.record_.rtcConfiguration = rtcConfiguration;
+    this.record_.constraints = constraints;
+  }
 
-    resetStats: function() {
-      this.record_.stats = {};
-    },
+  resetStats() {
+    this.record_.stats = {};
+  }
 
-    /**
-     * @param {string} dataSeriesId The TimelineDataSeries identifier.
-     * @return {!TimelineDataSeries}
-     */
-    getDataSeries: function(dataSeriesId) {
-      return this.record_.stats[dataSeriesId];
-    },
+  /**
+   * @param {string} dataSeriesId The TimelineDataSeries identifier.
+   * @return {!TimelineDataSeries}
+   */
+  getDataSeries(dataSeriesId) {
+    return this.record_.stats[dataSeriesId];
+  }
 
-    /**
-     * @param {string} dataSeriesId The TimelineDataSeries identifier.
-     * @param {!TimelineDataSeries} dataSeries The TimelineDataSeries to set to.
-     */
-    setDataSeries: function(dataSeriesId, dataSeries) {
-      this.record_.stats[dataSeriesId] = dataSeries;
-    },
+  /**
+   * @param {string} dataSeriesId The TimelineDataSeries identifier.
+   * @param {!TimelineDataSeries} dataSeries The TimelineDataSeries to set to.
+   */
+  setDataSeries(dataSeriesId, dataSeries) {
+    this.record_.stats[dataSeriesId] = dataSeries;
+  }
 
-    /**
-     * @param {!Object} update The object contains keys "time", "type", and
-     *   "value".
-     */
-    addUpdate: function(update) {
-      var time = new Date(parseFloat(update.time));
-      this.record_.updateLog.push({
-        time: time.toLocaleString(),
-        type: update.type,
-        value: update.value,
-      });
-    },
-  };
-
-  return PeerConnectionRecord;
-})();
-
-// The maximum number of data points bufferred for each stats. Old data points
-// will be shifted out when the buffer is full.
-var MAX_STATS_DATA_POINT_BUFFER_SIZE = 1000;
-
-// <include src="tab_view.js">
-// <include src="data_series.js">
-// <include src="ssrc_info_manager.js">
-// <include src="stats_graph_helper.js">
-// <include src="stats_rates_calculator.js">
-// <include src="stats_table.js">
-// <include src="peer_connection_update_table.js">
-// <include src="dump_creator.js">
-
+  /**
+   * @param {!Object} update The object contains keys "time", "type", and
+   *   "value".
+   */
+  addUpdate(update) {
+    var time = new Date(parseFloat(update.time));
+    this.record_.updateLog.push({
+      time: time.toLocaleString(),
+      type: update.type,
+      value: update.value,
+    });
+  }
+}
 
 function initialize() {
   dumpCreator = new DumpCreator($('content-root'));
   $('content-root').appendChild(createStatsSelectionOptionElements());
   tabView = new TabView($('content-root'));
   ssrcInfoManager = new SsrcInfoManager();
+  window.ssrcInfoManager = ssrcInfoManager;
   peerConnectionUpdateTable = new PeerConnectionUpdateTable();
   statsTable = new StatsTable(ssrcInfoManager);
 
-  chrome.send('finishedDOMLoad');
+  // Add listeners for all the updates that get sent from webrtc_internals.cc.
+  addWebUIListener('add-peer-connection', addPeerConnection);
+  addWebUIListener('update-peer-connection', updatePeerConnection);
+  addWebUIListener('update-all-peer-connections', updateAllPeerConnections);
+  addWebUIListener('remove-peer-connection', removePeerConnection);
+  addWebUIListener('add-standard-stats', addStandardStats);
+  addWebUIListener('add-legacy-stats', addLegacyStats);
+  addWebUIListener('add-get-user-media', addGetUserMedia);
+  addWebUIListener(
+      'remove-get-user-media-for-renderer', removeGetUserMediaForRenderer);
+  addWebUIListener(
+      'event-log-recordings-file-selection-cancelled',
+      eventLogRecordingsFileSelectionCancelled);
+  addWebUIListener(
+      'audio-debug-recordings-file-selection-cancelled',
+      audioDebugRecordingsFileSelectionCancelled);
+
+  // Request initial startup parameters.
+  sendWithPromise('finishedDOMLoad').then(params => {
+    if (params.audioDebugRecordingsEnabled) {
+      dumpCreator.setAudioDebugRecordingsCheckbox();
+    }
+    if (params.eventLogRecordingsEnabled) {
+      dumpCreator.setEventLogRecordingsCheckbox();
+    }
+    dumpCreator.setEventLogRecordingsCheckboxMutability(
+        params.eventLogRecordingsToggleable);
+  });
 
   // Requests stats from all peer connections every second.
   window.setInterval(requestStats, 1000);
@@ -460,31 +484,3 @@
 function eventLogRecordingsFileSelectionCancelled() {
   dumpCreator.clearEventLogRecordingsCheckbox();
 }
-
-
-/**
- * Notification that audio debug recordings are enabled. Used e.g. on page load
- * to update the UI to reflect the recording state.
- */
-function setAudioDebugRecordingsEnabled() {
-  dumpCreator.setAudioDebugRecordingsCheckbox();
-}
-
-
-/**
- * Notification that event log recordings are enabled. Used e.g. on page load
- * to update the UI to reflect the recording state.
- */
-function setEventLogRecordingsEnabled() {
-  dumpCreator.setEventLogRecordingsCheckbox();
-}
-
-
-/**
- * Notification that event log recordings may be turned off/on by the user.
- * Used e.g. on page load to update the UI to reflect the recording state's
- * mutability.
- */
-function setEventLogRecordingsToggleability(isToggleable) {
-  dumpCreator.setEventLogRecordingsCheckboxMutability(isToggleable);
-}
diff --git a/content/browser/webrtc/webrtc_internals.cc b/content/browser/webrtc/webrtc_internals.cc
index a9aff03..493ce07 100644
--- a/content/browser/webrtc/webrtc_internals.cc
+++ b/content/browser/webrtc/webrtc_internals.cc
@@ -69,26 +69,26 @@
 WebRTCInternals* WebRTCInternals::g_webrtc_internals = nullptr;
 
 WebRTCInternals::PendingUpdate::PendingUpdate(
-    const char* command,
-    std::unique_ptr<base::Value> value)
-    : command_(command), value_(std::move(value)) {}
+    const std::string& event_name,
+    std::unique_ptr<base::Value> event_data)
+    : event_name_(event_name), event_data_(std::move(event_data)) {}
 
 WebRTCInternals::PendingUpdate::PendingUpdate(PendingUpdate&& other)
-    : command_(other.command_),
-      value_(std::move(other.value_)) {}
+    : event_name_(other.event_name_),
+      event_data_(std::move(other.event_data_)) {}
 
 WebRTCInternals::PendingUpdate::~PendingUpdate() {
   DCHECK(thread_checker_.CalledOnValidThread());
 }
 
-const char* WebRTCInternals::PendingUpdate::command() const {
+const std::string& WebRTCInternals::PendingUpdate::event_name() const {
   DCHECK(thread_checker_.CalledOnValidThread());
-  return command_;
+  return event_name_;
 }
 
-const base::Value* WebRTCInternals::PendingUpdate::value() const {
+const base::Value* WebRTCInternals::PendingUpdate::event_data() const {
   DCHECK(thread_checker_.CalledOnValidThread());
-  return value_.get();
+  return event_data_.get();
 }
 
 WebRTCInternals::WebRTCInternals() : WebRTCInternals(500, true) {}
@@ -178,7 +178,7 @@
   dict->SetBoolean("connected", false);
 
   if (!observers_.empty())
-    SendUpdate("addPeerConnection", dict->CreateDeepCopy());
+    SendUpdate("add-peer-connection", dict->CreateDeepCopy());
 
   peer_connection_data_.Append(std::move(dict));
 
@@ -203,7 +203,7 @@
     std::unique_ptr<base::DictionaryValue> id(new base::DictionaryValue());
     id->SetInteger("pid", static_cast<int>(pid));
     id->SetInteger("lid", lid);
-    SendUpdate("removePeerConnection", std::move(id));
+    SendUpdate("remove-peer-connection", std::move(id));
   }
 }
 
@@ -243,7 +243,7 @@
   update->SetInteger("lid", lid);
   update->MergeDictionary(log_entry.get());
 
-  SendUpdate("updatePeerConnection", std::move(update));
+  SendUpdate("update-peer-connection", std::move(update));
 
   // Append the update to the end of the log.
   EnsureLogList(record)->Append(std::move(log_entry));
@@ -261,7 +261,7 @@
 
   dict->SetKey("reports", std::move(value));
 
-  SendUpdate("addStandardStats", std::move(dict));
+  SendUpdate("add-standard-stats", std::move(dict));
 }
 
 void WebRTCInternals::OnAddLegacyStats(base::ProcessId pid,
@@ -276,7 +276,7 @@
 
   dict->SetKey("reports", std::move(value));
 
-  SendUpdate("addLegacyStats", std::move(dict));
+  SendUpdate("add-legacy-stats", std::move(dict));
 }
 
 void WebRTCInternals::OnGetUserMedia(int rid,
@@ -305,7 +305,7 @@
     dict->SetString("video", video_constraints);
 
   if (!observers_.empty())
-    SendUpdate("addGetUserMedia", dict->CreateDeepCopy());
+    SendUpdate("add-get-user-media", dict->CreateDeepCopy());
 
   get_user_media_requests_.Append(std::move(dict));
 
@@ -352,10 +352,10 @@
 void WebRTCInternals::UpdateObserver(WebRTCInternalsUIObserver* observer) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (peer_connection_data_.GetSize() > 0)
-    observer->OnUpdate("updateAllPeerConnections", &peer_connection_data_);
+    observer->OnUpdate("update-all-peer-connections", &peer_connection_data_);
 
   for (const auto& request : get_user_media_requests_) {
-    observer->OnUpdate("addGetUserMedia", &request);
+    observer->OnUpdate("add-get-user-media", &request);
   }
 }
 
@@ -449,13 +449,13 @@
   return command_line_derived_logging_path_.empty();
 }
 
-void WebRTCInternals::SendUpdate(const char* command,
-                                 std::unique_ptr<base::Value> value) {
+void WebRTCInternals::SendUpdate(const std::string& event_name,
+                                 std::unique_ptr<base::Value> event_data) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(!observers_.empty());
 
   bool queue_was_empty = pending_updates_.empty();
-  pending_updates_.push(PendingUpdate(command, std::move(value)));
+  pending_updates_.push(PendingUpdate(event_name, std::move(event_data)));
 
   if (queue_was_empty) {
     GetUIThreadTaskRunner({})->PostDelayedTask(
@@ -502,10 +502,10 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   switch (selection_type_) {
     case SelectionType::kRtcEventLogs:
-      SendUpdate("eventLogRecordingsFileSelectionCancelled", nullptr);
+      SendUpdate("event-log-recordings-file-selection-cancelled", nullptr);
       break;
     case SelectionType::kAudioDebugRecordings:
-      SendUpdate("audioDebugRecordingsFileSelectionCancelled", nullptr);
+      SendUpdate("audio-debug-recordings-file-selection-cancelled", nullptr);
       break;
     default:
       NOTREACHED();
@@ -535,7 +535,7 @@
             new base::DictionaryValue());
         update->SetInteger("lid", lid);
         update->SetInteger("pid", pid);
-        SendUpdate("removePeerConnection", std::move(update));
+        SendUpdate("remove-peer-connection", std::move(update));
       }
       MaybeClosePeerConnection(record);
       peer_connection_data_.Remove(i, nullptr);
@@ -562,7 +562,7 @@
   if (found_any && !observers_.empty()) {
     std::unique_ptr<base::DictionaryValue> update(new base::DictionaryValue());
     update->SetInteger("rid", render_process_id);
-    SendUpdate("removeGetUserMediaForRenderer", std::move(update));
+    SendUpdate("remove-get-user-media-for-renderer", std::move(update));
   }
 }
 
@@ -659,7 +659,7 @@
   while (!pending_updates_.empty()) {
     const auto& update = pending_updates_.front();
     for (auto& observer : observers_)
-      observer.OnUpdate(update.command(), update.value());
+      observer.OnUpdate(update.event_name(), update.event_data());
     pending_updates_.pop();
   }
 }
diff --git a/content/browser/webrtc/webrtc_internals.h b/content/browser/webrtc/webrtc_internals.h
index a2761e98..90e5dfb 100644
--- a/content/browser/webrtc/webrtc_internals.h
+++ b/content/browser/webrtc/webrtc_internals.h
@@ -156,8 +156,8 @@
 
   static WebRTCInternals* g_webrtc_internals;
 
-  void SendUpdate(const char* command,
-                  std::unique_ptr<base::Value> value);
+  void SendUpdate(const std::string& event_name,
+                  std::unique_ptr<base::Value> event_data);
 
   // RenderProcessHostObserver implementation.
   void RenderProcessExited(RenderProcessHost* host,
@@ -269,18 +269,18 @@
   // thread.
   class PendingUpdate {
    public:
-    PendingUpdate(const char* command,
-                  std::unique_ptr<base::Value> value);
+    PendingUpdate(const std::string& event_name,
+                  std::unique_ptr<base::Value> event_data);
     PendingUpdate(PendingUpdate&& other);
     ~PendingUpdate();
 
-    const char* command() const;
-    const base::Value* value() const;
+    const std::string& event_name() const;
+    const base::Value* event_data() const;
 
    private:
     base::ThreadChecker thread_checker_;
-    const char* command_;
-    std::unique_ptr<base::Value> value_;
+    const std::string event_name_;
+    std::unique_ptr<base::Value> event_data_;
     DISALLOW_COPY_AND_ASSIGN(PendingUpdate);
   };
 
diff --git a/content/browser/webrtc/webrtc_internals_browsertest.cc b/content/browser/webrtc/webrtc_internals_browsertest.cc
index a9c3b6e..47f22367a 100644
--- a/content/browser/webrtc/webrtc_internals_browsertest.cc
+++ b/content/browser/webrtc/webrtc_internals_browsertest.cc
@@ -181,7 +181,8 @@
     std::stringstream ss;
     ss << "{pid:" << pc.pid_ <<", lid:" << pc.lid_ << ", " <<
            "url:'u', rtcConfiguration:'s', constraints:'c'}";
-    ASSERT_TRUE(ExecuteJavascript("addPeerConnection(" + ss.str() + ");"));
+    ASSERT_TRUE(ExecuteJavascript(
+        "cr.webUIListenerCallback('add-peer-connection', " + ss.str() + ");"));
   }
 
   // Execute the javascript of removePeerConnection.
@@ -189,7 +190,9 @@
     std::stringstream ss;
     ss << "{pid:" << pc.pid_ <<", lid:" << pc.lid_ << "}";
 
-    ASSERT_TRUE(ExecuteJavascript("removePeerConnection(" + ss.str() + ");"));
+    ASSERT_TRUE(ExecuteJavascript(
+        "cr.webUIListenerCallback('remove-peer-connection', " + ss.str() +
+        ");"));
   }
 
   // Execute the javascript of addGetUserMedia.
@@ -199,15 +202,17 @@
        << request.origin << "', audio:'" << request.audio_constraints
        << "', video:'" << request.video_constraints << "'}";
 
-    ASSERT_TRUE(ExecuteJavascript("addGetUserMedia(" + ss.str() + ");"));
+    ASSERT_TRUE(ExecuteJavascript(
+        "cr.webUIListenerCallback('add-get-user-media', " + ss.str() + ");"));
   }
 
   // Execute the javascript of removeGetUserMediaForRenderer.
   void ExecuteRemoveGetUserMediaForRendererJs(int rid) {
     std::stringstream ss;
     ss << "{rid:" << rid << "}";
-    ASSERT_TRUE(
-        ExecuteJavascript("removeGetUserMediaForRenderer(" + ss.str() + ");"));
+    ASSERT_TRUE(ExecuteJavascript(
+        "cr.webUIListenerCallback('remove-get-user-media-for-renderer', " +
+        ss.str() + ");"));
   }
 
   // Verifies that the DOM element with id |id| exists.
@@ -215,7 +220,8 @@
     bool result = false;
     ASSERT_TRUE(ExecuteScriptAndExtractBool(
         shell(),
-        "window.domAutomationController.send($('" + id + "') != null);",
+        "window.domAutomationController.send(document.getElementById('" + id +
+            "') != null);",
         &result));
     EXPECT_TRUE(result);
   }
@@ -225,7 +231,8 @@
     bool result = false;
     ASSERT_TRUE(ExecuteScriptAndExtractBool(
         shell(),
-        "window.domAutomationController.send($('" + id + "') == null);",
+        "window.domAutomationController.send(document.getElementById('" + id +
+            "') == null);",
         &result));
     EXPECT_TRUE(result);
   }
@@ -269,7 +276,8 @@
     ASSERT_TRUE(
         ExecuteScriptAndExtractBool(shell(),
                                     "window.domAutomationController.send("
-                                    "    $('user-media-tab-id') != null);",
+                                    "    document.querySelector("
+                                    "    '#user-media-tab-id') != null);",
                                     &user_media_tab_existed));
     EXPECT_EQ(!requests.empty(), user_media_tab_existed);
 
@@ -278,7 +286,8 @@
       ASSERT_TRUE(ExecuteScriptAndExtractInt(
           shell(),
           "window.domAutomationController.send("
-          "    $('user-media-tab-id').childNodes.length);",
+          "    document.querySelector('#user-media-tab-id')"
+          "        .childNodes.length);",
           &user_media_request_count));
       ASSERT_EQ(requests.size(), static_cast<size_t>(user_media_request_count));
     }
@@ -295,7 +304,9 @@
     string result;
     for (size_t i = 0; i < pc.events_.size(); ++i) {
       std::stringstream ss;
-      ss << "var row = $('" << log_id << "').rows[" << (i + 1) << "];"
+      ss << "var row = document.getElementById('" << log_id << "').rows["
+         << (i + 1)
+         << "];"
             "var cell = row.lastChild;"
             "window.domAutomationController.send(cell.firstChild.textContent);";
       ASSERT_TRUE(ExecuteScriptAndExtractString(shell(), ss.str(), &result));
@@ -311,7 +322,9 @@
     std::stringstream ss;
     ss << "{pid:" << pc.pid_ <<", lid:" << pc.lid_ <<
          ", type:'" << type << "', value:'" << value << "'}";
-    ASSERT_TRUE(ExecuteJavascript("updatePeerConnection(" + ss.str() + ")"));
+    ASSERT_TRUE(ExecuteJavascript(
+        "cr.webUIListenerCallback('update-peer-connection', " + ss.str() +
+        ")"));
 
     VerifyPeerConnectionEntry(pc);
   }
@@ -329,10 +342,10 @@
     }
     std::stringstream ss;
     ss << "(() => {\n";
-    ss << "  currentGetStatsMethod = OPTION_GETSTATS_LEGACY;\n";
-    ss << "  addLegacyStats({pid:" << pc.pid_ << ", lid:" << pc.lid_
-       << ", reports:[{id:'" << id << "', type:'" << type
-       << "', stats:" << stats.GetString() << "}]});\n";
+    ss << "  setCurrentGetStatsMethod(OPTION_GETSTATS_LEGACY);\n";
+    ss << "  cr.webUIListenerCallback('add-legacy-stats', "
+       << "{pid:" << pc.pid_ << ", lid:" << pc.lid_ << ", reports:[{id:'" << id
+       << "', type:'" << type << "', stats:" << stats.GetString() << "}]});\n";
     ss << "})()";
     ASSERT_TRUE(ExecuteJavascript(ss.str()));
     VerifyStatsTable(pc, entry);
@@ -363,10 +376,11 @@
     string result;
     ASSERT_TRUE(ExecuteScriptAndExtractString(
         shell(),
-        "var row = $('" + table_id + "-" + name + "');"
-        "var name = row.cells[0].textContent;"
-        "var value = row.cells[1].textContent;"
-        "window.domAutomationController.send(name + ':' + value)",
+        "var row = document.getElementById('" + table_id + "-" + name +
+            "');"
+            "var name = row.cells[0].textContent;"
+            "var value = row.cells[1].textContent;"
+            "window.domAutomationController.send(name + ':' + value)",
         &result));
     EXPECT_EQ(name + ":" + value, result);
   }
@@ -536,7 +550,9 @@
   pc_1.AddEvent("e4", "v4");
   string pc_array = "[" + pc_0.getAllUpdateString() + ", " +
                           pc_1.getAllUpdateString() + "]";
-  EXPECT_TRUE(ExecuteJavascript("updateAllPeerConnections(" + pc_array + ");"));
+  EXPECT_TRUE(ExecuteJavascript(
+      "cr.webUIListenerCallback('update-all-peer-connections', " + pc_array +
+      ");"));
   VerifyPeerConnectionEntry(pc_0);
   VerifyPeerConnectionEntry(pc_1);
 }
@@ -709,29 +725,33 @@
   ASSERT_TRUE(ExecuteScriptAndExtractInt(
       shell2,
       "window.domAutomationController.send("
-      "    $('peer-connections-list').getElementsByTagName('li').length);",
+      "    document.querySelector('#peer-connections-list')"
+      "        .getElementsByTagName('li').length);",
       &count));
   EXPECT_EQ(NUMBER_OF_PEER_CONNECTIONS, count);
 
   // Verifies the the event tables.
   ASSERT_TRUE(ExecuteScriptAndExtractInt(
       shell2,
-      "window.domAutomationController.send($('peer-connections-list')"
-      "    .getElementsByClassName('update-log-table').length);",
+      "window.domAutomationController.send("
+      "    document.querySelector('#peer-connections-list')"
+      "        .getElementsByClassName('update-log-table').length);",
       &count));
   EXPECT_EQ(NUMBER_OF_PEER_CONNECTIONS, count);
 
   ASSERT_TRUE(ExecuteScriptAndExtractInt(
       shell2,
-      "window.domAutomationController.send($('peer-connections-list')"
-      "    .getElementsByClassName('update-log-table')[0].rows.length);",
+      "window.domAutomationController.send("
+      "    document.querySelector('#peer-connections-list')"
+      "        .getElementsByClassName('update-log-table')[0].rows.length);",
       &count));
   EXPECT_GT(count, 1);
 
   ASSERT_TRUE(ExecuteScriptAndExtractInt(
       shell2,
-      "window.domAutomationController.send($('peer-connections-list')"
-      "    .getElementsByClassName('update-log-table')[1].rows.length);",
+      "window.domAutomationController.send("
+      "    document.querySelector('#peer-connections-list')"
+      "        .getElementsByClassName('update-log-table')[1].rows.length);",
       &count));
   EXPECT_GT(count, 1);
 
@@ -741,7 +761,8 @@
     ASSERT_TRUE(ExecuteScriptAndExtractInt(
         shell2,
         "window.domAutomationController.send("
-        "    $('peer-connections-list').getElementsByClassName("
+        "    document.querySelector('#peer-connections-list')"
+        "        .getElementsByClassName("
         "        'stats-table-container').length);",
         &count));
   }
@@ -750,7 +771,7 @@
   bool result = false;
   ASSERT_TRUE(ExecuteScriptAndExtractBool(
       shell2,
-      "var tableContainers = $('peer-connections-list')"
+      "var tableContainers = document.querySelector('#peer-connections-list')"
       "    .getElementsByClassName('stats-table-container');"
       "var result = true;"
       "for (var i = 0; i < tableContainers.length && result; ++i) {"
@@ -783,7 +804,9 @@
   pc_1.AddEvent("e4", "v4");
   string pc_array =
       "[" + pc_0.getAllUpdateString() + ", " + pc_1.getAllUpdateString() + "]";
-  EXPECT_TRUE(ExecuteJavascript("updateAllPeerConnections(" + pc_array + ");"));
+  EXPECT_TRUE(ExecuteJavascript(
+      "cr.webUIListenerCallback('update-all-peer-connections', " + pc_array +
+      ");"));
 
   // Verifies the peer connection data store can be created without stats.
   string dump_json;
diff --git a/content/browser/webrtc/webrtc_internals_message_handler.cc b/content/browser/webrtc/webrtc_internals_message_handler.cc
index 4292c7b..7b23402a 100644
--- a/content/browser/webrtc/webrtc_internals_message_handler.cc
+++ b/content/browser/webrtc/webrtc_internals_message_handler.cc
@@ -130,43 +130,37 @@
   }
 }
 
-void WebRTCInternalsMessageHandler::OnDOMLoadDone(
-    const base::ListValue* /* unused_list */) {
+void WebRTCInternalsMessageHandler::OnDOMLoadDone(const base::ListValue* args) {
+  std::string callback_id;
+  CHECK(args->GetString(0, &callback_id));
+  AllowJavascript();
+
   webrtc_internals_->UpdateObserver(this);
 
-  if (webrtc_internals_->IsAudioDebugRecordingsEnabled())
-    ExecuteJavascriptCommand("setAudioDebugRecordingsEnabled", nullptr);
+  base::Value params(base::Value::Type::DICTIONARY);
+  params.SetBoolKey("audioDebugRecordingsEnabled",
+                    webrtc_internals_->IsAudioDebugRecordingsEnabled());
+  params.SetBoolKey("eventLogRecordingsEnabled",
+                    webrtc_internals_->IsEventLogRecordingsEnabled());
+  params.SetBoolKey("eventLogRecordingsToggleable",
+                    webrtc_internals_->CanToggleEventLogRecordings());
 
-  if (webrtc_internals_->IsEventLogRecordingsEnabled())
-    ExecuteJavascriptCommand("setEventLogRecordingsEnabled", nullptr);
-
-  const base::Value can_toggle(
-      webrtc_internals_->CanToggleEventLogRecordings());
-  ExecuteJavascriptCommand("setEventLogRecordingsToggleability", &can_toggle);
+  ResolveJavascriptCallback(base::Value(callback_id), std::move(params));
 }
 
-void WebRTCInternalsMessageHandler::OnUpdate(const char* command,
-                                             const base::Value* args) {
-  ExecuteJavascriptCommand(command, args);
-}
-
-// TODO(eladalon): Make this function accept a vector of base::Values.
-// https://crbug.com/817384
-void WebRTCInternalsMessageHandler::ExecuteJavascriptCommand(
-    const char* command,
-    const base::Value* args) {
+void WebRTCInternalsMessageHandler::OnUpdate(const std::string& event_name,
+                                             const base::Value* event_data) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   RenderFrameHost* host = GetWebRTCInternalsHost();
   if (!host)
     return;
 
-  std::vector<const base::Value*> args_vector;
-  if (args)
-    args_vector.push_back(args);
-
-  base::string16 script = WebUI::GetJavascriptCall(command, args_vector);
-  host->ExecuteJavaScript(script, base::NullCallback());
+  if (event_data) {
+    FireWebUIListener(event_name, *event_data);
+  } else {
+    FireWebUIListener(event_name, base::Value());
+  }
 }
 
 }  // namespace content
diff --git a/content/browser/webrtc/webrtc_internals_message_handler.h b/content/browser/webrtc/webrtc_internals_message_handler.h
index 4e5943d9..670fda9b 100644
--- a/content/browser/webrtc/webrtc_internals_message_handler.h
+++ b/content/browser/webrtc/webrtc_internals_message_handler.h
@@ -51,7 +51,8 @@
   void OnDOMLoadDone(const base::ListValue* list);
 
   // WebRTCInternalsUIObserver override.
-  void OnUpdate(const char* command, const base::Value* args) override;
+  void OnUpdate(const std::string& event_name,
+                const base::Value* event_data) override;
 
   // Executes Javascript command.
   void ExecuteJavascriptCommand(const char* command, const base::Value* args);
diff --git a/content/browser/webrtc/webrtc_internals_ui.cc b/content/browser/webrtc/webrtc_internals_ui.cc
index bd58942c..ea83883 100644
--- a/content/browser/webrtc/webrtc_internals_ui.cc
+++ b/content/browser/webrtc/webrtc_internals_ui.cc
@@ -5,6 +5,7 @@
 #include "content/browser/webrtc/webrtc_internals_ui.h"
 
 #include "content/browser/webrtc/resources/grit/webrtc_internals_resources.h"
+#include "content/browser/webrtc/resources/grit/webrtc_internals_resources_map.h"
 #include "content/browser/webrtc/webrtc_internals_message_handler.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui.h"
@@ -19,8 +20,9 @@
       WebUIDataSource::Create(kChromeUIWebRTCInternalsHost);
 
   source->UseStringsJs();
-  source->AddResourcePath("webrtc_internals.js", IDR_WEBRTC_INTERNALS_JS);
-  source->SetDefaultResource(IDR_WEBRTC_INTERNALS_HTML);
+  source->AddResourcePaths(base::make_span(kWebrtcInternalsResources,
+                                           kWebrtcInternalsResourcesSize));
+  source->SetDefaultResource(IDR_WEBRTC_INTERNALS_WEBRTC_INTERNALS_HTML);
   return source;
 }
 
diff --git a/content/browser/webrtc/webrtc_internals_ui_observer.h b/content/browser/webrtc/webrtc_internals_ui_observer.h
index 8c8d381..171aaf6 100644
--- a/content/browser/webrtc/webrtc_internals_ui_observer.h
+++ b/content/browser/webrtc/webrtc_internals_ui_observer.h
@@ -18,10 +18,10 @@
  public:
   virtual ~WebRTCInternalsUIObserver() {}
 
-  // This is called on the browser IO thread. |args| can be NULL if there are no
-  // arguments.
-  virtual void OnUpdate(const char* command,
-                        const base::Value* args) = 0;
+  // This is called on the browser IO thread. |event_data| can be NULL if there
+  // are no arguments.
+  virtual void OnUpdate(const std::string& event_name,
+                        const base::Value* event_data) = 0;
 };
 
 }  // namespace content
diff --git a/content/browser/webrtc/webrtc_internals_unittest.cc b/content/browser/webrtc/webrtc_internals_unittest.cc
index 8bd500c..f23bbb4 100644
--- a/content/browser/webrtc/webrtc_internals_unittest.cc
+++ b/content/browser/webrtc/webrtc_internals_unittest.cc
@@ -33,24 +33,25 @@
 
 class MockWebRtcInternalsProxy : public WebRTCInternalsUIObserver {
  public:
-  MockWebRtcInternalsProxy() : loop_(nullptr) {}
+  MockWebRtcInternalsProxy() = default;
   explicit MockWebRtcInternalsProxy(base::RunLoop* loop) : loop_(loop) {}
 
-  const std::string& command() const { return command_; }
+  const std::string& event_name() const { return event_name_; }
 
-  base::Value* value() { return value_.get(); }
+  base::Value* event_data() { return event_data_.get(); }
 
  private:
-  void OnUpdate(const char* command, const base::Value* value) override {
-    command_ = command;
-    value_.reset(value ? value->DeepCopy() : nullptr);
+  void OnUpdate(const std::string& event_name,
+                const base::Value* event_data) override {
+    event_name_ = event_name;
+    event_data_.reset(event_data ? event_data->DeepCopy() : nullptr);
     if (loop_)
       loop_->Quit();
   }
 
-  std::string command_;
-  std::unique_ptr<base::Value> value_;
-  base::RunLoop* loop_;
+  std::string event_name_;
+  std::unique_ptr<base::Value> event_data_;
+  base::RunLoop* loop_{nullptr};
 };
 
 class MockWakeLock : public device::mojom::WakeLock {
@@ -178,7 +179,7 @@
   GetUIThreadTaskRunner({})->PostTask(FROM_HERE, loop.QuitClosure());
   loop.Run();
 
-  EXPECT_EQ("", observer.command());
+  EXPECT_EQ("", observer.event_name());
 
   webrtc_internals.OnRemovePeerConnection(3, 4);
 
@@ -197,10 +198,10 @@
   // Make sure we don't have a log entry since there was no observer.
   MockWebRtcInternalsProxy observer;
   webrtc_internals.UpdateObserver(&observer);
-  EXPECT_EQ("updateAllPeerConnections", observer.command());
+  EXPECT_EQ("update-all-peer-connections", observer.event_name());
 
   base::ListValue* list = nullptr;
-  ASSERT_TRUE(observer.value()->GetAsList(&list));
+  ASSERT_TRUE(observer.event_data()->GetAsList(&list));
   EXPECT_EQ(1U, list->GetSize());
   base::DictionaryValue* dict = nullptr;
   ASSERT_TRUE((*list->begin()).GetAsDictionary(&dict));
@@ -225,10 +226,10 @@
 
   // Make sure we have a log entry since there was an observer.
   webrtc_internals.UpdateObserver(&observer);
-  EXPECT_EQ("updateAllPeerConnections", observer.command());
+  EXPECT_EQ("update-all-peer-connections", observer.event_name());
 
   base::ListValue* list = nullptr;
-  ASSERT_TRUE(observer.value()->GetAsList(&list));
+  ASSERT_TRUE(observer.event_data()->GetAsList(&list));
   EXPECT_EQ(1U, list->GetSize());
   base::DictionaryValue* dict = nullptr;
   ASSERT_TRUE((*list->begin()).GetAsDictionary(&dict));
@@ -238,9 +239,9 @@
   // Make sure we the log entry was removed when the last observer was removed.
   webrtc_internals.RemoveObserver(&observer);
   webrtc_internals.UpdateObserver(&observer);
-  EXPECT_EQ("updateAllPeerConnections", observer.command());
+  EXPECT_EQ("update-all-peer-connections", observer.event_name());
 
-  ASSERT_TRUE(observer.value()->GetAsList(&list));
+  ASSERT_TRUE(observer.event_data()->GetAsList(&list));
   EXPECT_EQ(1U, list->GetSize());
   ASSERT_TRUE((*list->begin()).GetAsDictionary(&dict));
   ASSERT_FALSE(dict->GetList("log", &log));
@@ -260,10 +261,10 @@
 
   loop.Run();
 
-  ASSERT_EQ("addPeerConnection", observer.command());
+  ASSERT_EQ("add-peer-connection", observer.event_name());
 
   base::DictionaryValue* dict = nullptr;
-  EXPECT_TRUE(observer.value()->GetAsDictionary(&dict));
+  EXPECT_TRUE(observer.event_data()->GetAsDictionary(&dict));
 
   VerifyInt(dict, "pid", 1);
   VerifyInt(dict, "lid", 2);
@@ -288,10 +289,10 @@
 
   loop.Run();
 
-  ASSERT_EQ("removePeerConnection", observer.command());
+  ASSERT_EQ("remove-peer-connection", observer.event_name());
 
   base::DictionaryValue* dict = nullptr;
-  EXPECT_TRUE(observer.value()->GetAsDictionary(&dict));
+  EXPECT_TRUE(observer.event_data()->GetAsDictionary(&dict));
 
   VerifyInt(dict, "pid", 1);
   VerifyInt(dict, "lid", 2);
@@ -315,10 +316,10 @@
 
   loop.Run();
 
-  ASSERT_EQ("updatePeerConnection", observer.command());
+  ASSERT_EQ("update-peer-connection", observer.event_name());
 
   base::DictionaryValue* dict = nullptr;
-  EXPECT_TRUE(observer.value()->GetAsDictionary(&dict));
+  EXPECT_TRUE(observer.event_data()->GetAsDictionary(&dict));
 
   VerifyInt(dict, "pid", 1);
   VerifyInt(dict, "lid", 2);
@@ -352,9 +353,9 @@
 
   loop.Run();
 
-  ASSERT_EQ("addGetUserMedia", observer.command());
-  VerifyGetUserMediaData(observer.value(), rid, pid, kUrl, audio_constraint,
-                         video_constraint);
+  ASSERT_EQ("add-get-user-media", observer.event_name());
+  VerifyGetUserMediaData(observer.event_data(), rid, pid, kUrl,
+                         audio_constraint, video_constraint);
 
   webrtc_internals.RemoveObserver(&observer);
 
@@ -375,9 +376,9 @@
   webrtc_internals.AddObserver(&observer);
   webrtc_internals.UpdateObserver(&observer);
 
-  EXPECT_EQ("addGetUserMedia", observer.command());
-  VerifyGetUserMediaData(observer.value(), rid, pid, kUrl, audio_constraint,
-                         video_constraint);
+  EXPECT_EQ("add-get-user-media", observer.event_name());
+  VerifyGetUserMediaData(observer.event_data(), rid, pid, kUrl,
+                         audio_constraint, video_constraint);
 
   webrtc_internals.RemoveObserver(&observer);
 
@@ -400,11 +401,11 @@
 
   webrtc_internals.UpdateObserver(&observer);
 
-  EXPECT_EQ("updateAllPeerConnections", observer.command());
-  ASSERT_TRUE(observer.value());
+  EXPECT_EQ("update-all-peer-connections", observer.event_name());
+  ASSERT_TRUE(observer.event_data());
 
   base::ListValue* list = nullptr;
-  EXPECT_TRUE(observer.value()->GetAsList(&list));
+  EXPECT_TRUE(observer.event_data()->GetAsList(&list));
   EXPECT_EQ(1U, list->GetSize());
 
   base::DictionaryValue* dict = nullptr;
@@ -449,11 +450,11 @@
 
   loop.Run();
 
-  EXPECT_EQ("addStandardStats", observer.command());
-  ASSERT_TRUE(observer.value());
+  EXPECT_EQ("add-standard-stats", observer.event_name());
+  ASSERT_TRUE(observer.event_data());
 
   base::DictionaryValue* dict = nullptr;
-  EXPECT_TRUE(observer.value()->GetAsDictionary(&dict));
+  EXPECT_TRUE(observer.event_data()->GetAsDictionary(&dict));
 
   VerifyInt(dict, "pid", pid);
   VerifyInt(dict, "lid", lid);
@@ -480,11 +481,11 @@
 
   loop.Run();
 
-  EXPECT_EQ("addLegacyStats", observer.command());
-  ASSERT_TRUE(observer.value());
+  EXPECT_EQ("add-legacy-stats", observer.event_name());
+  ASSERT_TRUE(observer.event_data());
 
   base::DictionaryValue* dict = nullptr;
-  EXPECT_TRUE(observer.value()->GetAsDictionary(&dict));
+  EXPECT_TRUE(observer.event_data()->GetAsDictionary(&dict));
 
   VerifyInt(dict, "pid", pid);
   VerifyInt(dict, "lid", lid);
@@ -504,8 +505,9 @@
 
   loop.Run();
 
-  EXPECT_EQ("audioDebugRecordingsFileSelectionCancelled", observer.command());
-  EXPECT_EQ(nullptr, observer.value());
+  EXPECT_EQ("audio-debug-recordings-file-selection-cancelled",
+            observer.event_name());
+  EXPECT_EQ(nullptr, observer.event_data());
 
   base::RunLoop().RunUntilIdle();
 }
diff --git a/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc b/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
index 428c44e..a3c93dd 100644
--- a/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
+++ b/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
@@ -60,8 +60,7 @@
       factory_->CreateWorkerHost(
           blink::DedicatedWorkerToken(),
           browser_interface_broker_.BindNewPipeAndPassReceiver(),
-          remote_host_.BindNewPipeAndPassReceiver(),
-          base::BindOnce([](const network::CrossOriginEmbedderPolicy&) {}));
+          remote_host_.BindNewPipeAndPassReceiver(), base::DoNothing());
     }
   }
 
diff --git a/content/child/child_thread_impl.cc b/content/child/child_thread_impl.cc
index e111dc4e..18e0005 100644
--- a/content/child/child_thread_impl.cc
+++ b/content/child/child_thread_impl.cc
@@ -537,13 +537,23 @@
 
 void ChildThreadImpl::SetFieldTrialGroup(const std::string& trial_name,
                                          const std::string& group_name) {
-  if (field_trial_syncer_)
-    field_trial_syncer_->OnSetFieldTrialGroup(trial_name, group_name);
+  if (!field_trial_syncer_)
+    return;
+
+  handling_set_field_trial_group_notification_ = true;
+  field_trial_syncer_->OnSetFieldTrialGroup(trial_name, group_name);
+  handling_set_field_trial_group_notification_ = false;
 }
 
 void ChildThreadImpl::OnFieldTrialGroupFinalized(
     const std::string& trial_name,
     const std::string& group_name) {
+  // If we're currently in SetFieldTrialGroup(), it's a field trial the browser
+  // is telling us about. Don't send a mojo request back to the browser, since
+  // it's unnecessary.
+  if (handling_set_field_trial_group_notification_)
+    return;
+
   mojo::Remote<mojom::FieldTrialRecorder> field_trial_recorder;
   BindHostReceiver(field_trial_recorder.BindNewPipeAndPassReceiver());
   field_trial_recorder->FieldTrialActivated(trial_name);
diff --git a/content/child/child_thread_impl.h b/content/child/child_thread_impl.h
index 5e23aad..49da2478 100644
--- a/content/child/child_thread_impl.h
+++ b/content/child/child_thread_impl.h
@@ -224,6 +224,9 @@
   scoped_refptr<base::SingleThreadTaskRunner> browser_process_io_runner_;
 
   std::unique_ptr<variations::ChildProcessFieldTrialSyncer> field_trial_syncer_;
+  // Whether we're handling the SetFieldTrialGroup() notification from the
+  // browser process.
+  bool handling_set_field_trial_group_notification_ = false;
 
   std::unique_ptr<base::WeakPtrFactory<ChildThreadImpl>>
       channel_connected_factory_;
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index b094f87..07c6941 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -483,10 +483,6 @@
           cpp = "::content::ContentSecurityPolicy"
         },
         {
-          mojom = "content.mojom.NavigationDownloadPolicy"
-          cpp = "::blink::NavigationDownloadPolicy"
-        },
-        {
           mojom = "content.mojom.NavigationGesture"
           cpp = "::content::NavigationGesture"
         },
@@ -595,10 +591,6 @@
           cpp = "::blink::Impression"
         },
         {
-          mojom = "content.mojom.NavigationDownloadPolicy"
-          cpp = "::blink::NavigationDownloadPolicy"
-        },
-        {
           mojom = "content.mojom.PageState"
           cpp = "::blink::PageState"
         },
@@ -615,7 +607,6 @@
         "//content/common/content_param_traits.h",
         "//content/common/navigation_params.h",
         "//third_party/blink/public/common/navigation/impression.h",
-        "//third_party/blink/public/common/navigation/navigation_policy.h",
         "//third_party/blink/public/common/page_state/page_state.h",
         "//ui/base/page_transition_types.h",
       ]
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h
index 9306d9ca5..eb71205 100644
--- a/content/common/frame_messages.h
+++ b/content/common/frame_messages.h
@@ -95,12 +95,6 @@
 IPC_ENUM_TRAITS_MAX_VALUE(blink::mojom::WebFeature,
                           blink::mojom::WebFeature::kMaxValue)
 
-IPC_STRUCT_TRAITS_BEGIN(blink::NavigationDownloadPolicy)
-  IPC_STRUCT_TRAITS_MEMBER(observed_types)
-  IPC_STRUCT_TRAITS_MEMBER(disallowed_types)
-  IPC_STRUCT_TRAITS_MEMBER(blocking_downloads_in_sandbox_enabled)
-IPC_STRUCT_TRAITS_END()
-
 IPC_STRUCT_TRAITS_BEGIN(blink::Impression)
   IPC_STRUCT_TRAITS_MEMBER(conversion_destination)
   IPC_STRUCT_TRAITS_MEMBER(reporting_origin)
diff --git a/content/common/navigation_params.mojom b/content/common/navigation_params.mojom
index 3ec12da..39d580f 100644
--- a/content/common/navigation_params.mojom
+++ b/content/common/navigation_params.mojom
@@ -17,6 +17,7 @@
 import "services/network/public/mojom/url_request.mojom";
 import "services/network/public/mojom/url_response_head.mojom";
 import "services/network/public/mojom/web_client_hints_types.mojom";
+import "third_party/blink/public/mojom/navigation/navigation_policy.mojom";
 import "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom";
 import "third_party/blink/public/mojom/fetch/fetch_api_request.mojom";
 import "third_party/blink/public/mojom/frame/frame_policy.mojom";
@@ -31,9 +32,6 @@
 struct Impression;
 
 [Native]
-struct NavigationDownloadPolicy;
-
-[Native]
 struct PageState;
 
 [Native]
@@ -171,7 +169,7 @@
   NavigationType navigation_type = DIFFERENT_DOCUMENT;
 
   // Governs how downloads are handled by this navigation.
-  NavigationDownloadPolicy download_policy;
+  blink.mojom.NavigationDownloadPolicy download_policy;
 
   // Informs the RenderView the pending navigation should replace the current
   // history entry when it commits.  This is used by client-side redirects to
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index 7e7c436..3a3c6908 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -185,6 +185,7 @@
     "hid_chooser.h",
     "hid_delegate.h",
     "histogram_fetcher.h",
+    "identity_request_dialog_controller.cc",
     "identity_request_dialog_controller.h",
     "idle_manager.h",
     "installability_error.cc",
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 8b699a6..8fd655e 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -534,7 +534,7 @@
     const GURL& site) {
   DCHECK(browser_context);
 
-  return StoragePartitionConfig::CreateDefault();
+  return StoragePartitionConfig::CreateDefault(browser_context);
 }
 
 MediaObserver* ContentBrowserClient::GetMediaObserver() {
@@ -987,6 +987,8 @@
 }
 
 void ContentBrowserClient::OnNetworkServiceDataUseUpdate(
+    int process_id,
+    int route_id,
     int32_t network_traffic_annotation_id_hash,
     int64_t recv_bytes,
     int64_t sent_bytes) {}
@@ -1186,7 +1188,7 @@
 
 std::unique_ptr<IdentityRequestDialogController>
 ContentBrowserClient::CreateIdentityRequestDialogController() {
-  return nullptr;
+  return std::make_unique<IdentityRequestDialogController>();
 }
 
 bool ContentBrowserClient::SuppressDifferentOriginSubframeJSDialogs(
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index bd19bd6..372fdf2b 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -1765,6 +1765,8 @@
   // Called on every request completion to update the data use when network
   // service is enabled.
   virtual void OnNetworkServiceDataUseUpdate(
+      int process_id,
+      int route_id,
       int32_t network_traffic_annotation_id_hash,
       int64_t recv_bytes,
       int64_t sent_bytes);
diff --git a/content/public/browser/identity_request_dialog_controller.cc b/content/public/browser/identity_request_dialog_controller.cc
new file mode 100644
index 0000000..caef596f2
--- /dev/null
+++ b/content/public/browser/identity_request_dialog_controller.cc
@@ -0,0 +1,38 @@
+// 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 "content/public/browser/identity_request_dialog_controller.h"
+
+#include <memory>
+
+#include "content/public/browser/web_contents.h"
+#include "url/gurl.h"
+
+namespace content {
+
+void IdentityRequestDialogController::ShowInitialPermissionDialog(
+    WebContents* rp_web_contents,
+    const GURL& idp_url,
+    InitialApprovalCallback approval_callback) {
+  std::move(approval_callback).Run(UserApproval::kDenied);
+}
+
+void IdentityRequestDialogController::ShowIdProviderWindow(
+    content::WebContents* rp_web_contents,
+    content::WebContents* idp_web_contents,
+    const GURL& idp_signin_url,
+    IdProviderWindowClosedCallback on_closed) {
+  std::move(on_closed).Run();
+}
+
+void IdentityRequestDialogController::CloseIdProviderWindow() {}
+
+void IdentityRequestDialogController::ShowTokenExchangePermissionDialog(
+    content::WebContents* rp_web_contents,
+    const GURL& idp_url,
+    TokenExchangeApprovalCallback approval_callback) {
+  std::move(approval_callback).Run(UserApproval::kDenied);
+}
+
+}  // namespace content
diff --git a/content/public/browser/identity_request_dialog_controller.h b/content/public/browser/identity_request_dialog_controller.h
index a61c699..bca5492 100644
--- a/content/public/browser/identity_request_dialog_controller.h
+++ b/content/public/browser/identity_request_dialog_controller.h
@@ -50,7 +50,7 @@
   virtual void ShowInitialPermissionDialog(
       WebContents* rp_web_contents,
       const GURL& idp_url,
-      InitialApprovalCallback approval_callback) = 0;
+      InitialApprovalCallback approval_callback);
 
   // Shows the identity provider sign-in page at the given URL using the
   // |idp_web_contents| inside a modal window. The |on_closed| callback is
@@ -58,14 +58,13 @@
   // invoking CloseIdProviderWindow().
   //
   // 'IdentityRequestDialogController' is destroyed before either WebContents.
-  virtual void ShowIdProviderWindow(
-      content::WebContents* rp_web_contents,
-      content::WebContents* idp_web_contents,
-      const GURL& idp_signin_url,
-      IdProviderWindowClosedCallback on_closed) = 0;
+  virtual void ShowIdProviderWindow(content::WebContents* rp_web_contents,
+                                    content::WebContents* idp_web_contents,
+                                    const GURL& idp_signin_url,
+                                    IdProviderWindowClosedCallback on_closed);
 
   // Closes the identity provider sign-in window if any.
-  virtual void CloseIdProviderWindow() = 0;
+  virtual void CloseIdProviderWindow();
 
   // Shows the secondary permission dialog to the user.
   // - |rp_web_contents| is the RP web contents that has initiated the
@@ -76,7 +75,7 @@
   virtual void ShowTokenExchangePermissionDialog(
       content::WebContents* rp_web_contents,
       const GURL& idp_url,
-      TokenExchangeApprovalCallback approval_callback) = 0;
+      TokenExchangeApprovalCallback approval_callback);
 };
 
 }  // namespace content
diff --git a/content/public/browser/storage_partition_config.cc b/content/public/browser/storage_partition_config.cc
index c8883dfe..f885c1ed 100644
--- a/content/public/browser/storage_partition_config.cc
+++ b/content/public/browser/storage_partition_config.cc
@@ -5,6 +5,7 @@
 #include "content/public/browser/storage_partition_config.h"
 
 #include "base/check.h"
+#include "content/public/browser/browser_context.h"
 
 namespace content {
 
@@ -14,12 +15,14 @@
     const StoragePartitionConfig&) = default;
 
 // static
-StoragePartitionConfig StoragePartitionConfig::CreateDefault() {
-  return StoragePartitionConfig("", "", false);
+StoragePartitionConfig StoragePartitionConfig::CreateDefault(
+    BrowserContext* browser_context) {
+  return StoragePartitionConfig("", "", browser_context->IsOffTheRecord());
 }
 
 // static
 StoragePartitionConfig StoragePartitionConfig::Create(
+    BrowserContext* browser_context,
     const std::string& partition_domain,
     const std::string& partition_name,
     bool in_memory) {
@@ -27,7 +30,8 @@
   // wrong or the calling code is not explicitly signalling its desire to create
   // a default partition by calling CreateDefault().
   CHECK(!partition_domain.empty());
-  return StoragePartitionConfig(partition_domain, partition_name, in_memory);
+  return StoragePartitionConfig(partition_domain, partition_name,
+                                in_memory || browser_context->IsOffTheRecord());
 }
 
 StoragePartitionConfig::StoragePartitionConfig(
@@ -38,17 +42,6 @@
       partition_name_(partition_name),
       in_memory_(in_memory) {}
 
-StoragePartitionConfig StoragePartitionConfig::CopyWithInMemorySet() const {
-  if (in_memory_)
-    return *this;
-
-  auto result = StoragePartitionConfig(partition_domain_, partition_name_,
-                                       true /* in_memory */);
-  result.set_fallback_to_partition_domain_for_blob_urls(
-      fallback_to_partition_domain_for_blob_urls_);
-  return result;
-}
-
 base::Optional<StoragePartitionConfig>
 StoragePartitionConfig::GetFallbackForBlobUrls() const {
   if (fallback_to_partition_domain_for_blob_urls_ == FallbackMode::kNone)
diff --git a/content/public/browser/storage_partition_config.h b/content/public/browser/storage_partition_config.h
index d375470..408a5fa 100644
--- a/content/public/browser/storage_partition_config.h
+++ b/content/public/browser/storage_partition_config.h
@@ -12,6 +12,7 @@
 #include "content/common/content_export.h"
 
 namespace content {
+class BrowserContext;
 
 // Each StoragePartition is uniquely identified by which partition domain
 // it belongs to (such as an app or the browser itself), the user supplied
@@ -23,7 +24,9 @@
   StoragePartitionConfig(const StoragePartitionConfig&);
   StoragePartitionConfig& operator=(const StoragePartitionConfig&);
 
-  static StoragePartitionConfig CreateDefault();
+  // Creates a default config for |browser_context|. If |browser_context| is an
+  // off-the-record profile, then the config will have |in_memory_| set to true.
+  static StoragePartitionConfig CreateDefault(BrowserContext* browser_context);
 
   // Creates a config tied to a specific domain.
   // The |partition_domain| is [a-z]* UTF-8 string, specifying the domain in
@@ -31,8 +34,12 @@
   // be an empty string. Within a domain, partitions can be uniquely identified
   // by the combination of |partition_name| and |in_memory| values. When a
   // partition is not to be persisted, the |in_memory| value must be set to
-  // true.
-  static StoragePartitionConfig Create(const std::string& partition_domain,
+  // true. If |browser_context| is an off-the-record profile, then the config
+  // will have |in_memory_| set to true independent of what is specified in
+  // the |in_memory| parameter. This is because these profiles are not allowed
+  // to persist information on disk.
+  static StoragePartitionConfig Create(BrowserContext* browser_context,
+                                       const std::string& partition_domain,
                                        const std::string& partition_name,
                                        bool in_memory);
 
@@ -44,10 +51,6 @@
   // a copy of a config created with that method.
   bool is_default() const { return partition_domain_.empty(); }
 
-  // Returns a copy of this config that has the same partition_domain
-  // and partition_name, but the in_memeory field is always set to true.
-  StoragePartitionConfig CopyWithInMemorySet() const;
-
   // In some cases we want a "child" storage partition to resolve blob URLs that
   // were created by their "parent", while not allowing the reverse. To enable
   // this, set this flag to a value other than kNone, which will result in the
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc
index 9bcbc3a..29bbdaf 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.cc
+++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -481,16 +481,31 @@
   TRACE_EVENT2("accessibility", "BlinkAXTreeSource::SerializeNode", "role",
                ui::ToString(dst->role), "id", dst->id);
 
-  SerializeNameAndDescriptionAttributes(src, dst);
-
   if (accessibility_mode_.has_mode(ui::AXMode::kPDF)) {
+    SerializeNameAndDescriptionAttributes(src, dst);
     // Return early. None of the following attributes are needed for PDFs.
     return;
   }
 
+  // Bounding boxes are needed on all nodes, including ignored, for hit testing.
   SerializeBoundingBoxAttributes(src, dst);
   cached_bounding_boxes_[dst->id] = dst->relative_bounds;
 
+  // Return early. The following attributes are unnecessary for ignored nodes.
+  // Exception: focusable ignored nodes are fully serialized, so that reasonable
+  // verbalizations can be made if they actually receive focus.
+  if (src.AccessibilityIsIgnored() &&
+      !dst->HasState(ax::mojom::State::kFocusable)) {
+    // The name is important for exposing the selection around ignored nodes.
+    // TODO(accessibility) Remove this and still pass this content_browsertest:
+    // All/DumpAccessibilityTreeTest.AccessibilityIgnoredSelection/blink
+    if (src.Role() == ax::mojom::Role::kStaticText)
+      SerializeNameAndDescriptionAttributes(src, dst);
+    return;
+  }
+
+  SerializeNameAndDescriptionAttributes(src, dst);
+
   if (accessibility_mode_.has_mode(ui::AXMode::kScreenReader)) {
     if (src.IsInLiveRegion())
       SerializeLiveRegionAttributes(src, dst);
@@ -760,11 +775,6 @@
     dst->AddBoolAttribute(ax::mojom::BoolAttribute::kModal, src.IsModal());
   }
 
-  if (ui::IsPlatformDocument(dst->role)) {
-    TruncateAndAddStringAttribute(dst, ax::mojom::StringAttribute::kHtmlTag,
-                                  "#document");
-  }
-
   if (ui::IsImage(dst->role))
     AddImageAnnotations(src, dst);
 
diff --git a/content/test/data/accessibility/aria/aria-describedby-updates-expected-blink.txt b/content/test/data/accessibility/aria/aria-describedby-updates-expected-blink.txt
index 4a6baf94..55dd8ee 100644
--- a/content/test/data/accessibility/aria/aria-describedby-updates-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-describedby-updates-expected-blink.txt
@@ -3,4 +3,4 @@
 ++++genericContainer ignored
 ++++++main description='oranges'
 ++++++++paragraph ignored invisible
-++++++++++staticText ignored invisible name='oranges'
+++++++++++staticText ignored invisible name='oranges'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-hidden-changed-expected-blink.txt b/content/test/data/accessibility/aria/aria-hidden-changed-expected-blink.txt
index 9e906421..22bc0cd 100644
--- a/content/test/data/accessibility/aria/aria-hidden-changed-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-hidden-changed-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea name='done'
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++heading ignored invisible name='Stay hidden'
+++++++heading ignored invisible
 ++++++++staticText ignored invisible name='Stay hidden'
-++++++heading ignored invisible name='To hidden'
-++++++++staticText ignored invisible name='To hidden'
+++++++heading ignored invisible
+++++++++staticText ignored invisible name='To hidden'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-hidden-descendant-tabindex-change-expected-blink.txt b/content/test/data/accessibility/aria/aria-hidden-descendant-tabindex-change-expected-blink.txt
index cc8d900..fcbcc97 100644
--- a/content/test/data/accessibility/aria/aria-hidden-descendant-tabindex-change-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-hidden-descendant-tabindex-change-expected-blink.txt
@@ -4,7 +4,7 @@
 ++++++genericContainer ignored invisible
 ++++++++group ignored invisible
 ++++++++++group ignored invisible
-++++++++++++slider ignored invisible name='Slider#1 now unfocusable'
+++++++++++++slider ignored invisible
 ++++++genericContainer ignored invisible
 ++++++++group ignored invisible
 ++++++++++group ignored invisible
diff --git a/content/test/data/accessibility/aria/aria-hidden-descendants-expected-blink.txt b/content/test/data/accessibility/aria/aria-hidden-descendants-expected-blink.txt
index a65d09a..1764292 100644
--- a/content/test/data/accessibility/aria/aria-hidden-descendants-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-hidden-descendants-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++group ignored invisible name='Subtree starts visible ends hidden'
+++++++group ignored invisible
 ++++++++group ignored invisible
 ++++++++++group ignored invisible
 ++++++++++++slider horizontal invisible name='Slider#1 now inside hidden subtree'
@@ -9,4 +9,4 @@
 ++++++++group
 ++++++++++group
 ++++++++++++slider horizontal name='Slider #2 now inside visible subtree'
-++++++group name='finished'
+++++++group name='finished'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-hidden-described-by-expected-blink.txt b/content/test/data/accessibility/aria/aria-hidden-described-by-expected-blink.txt
index 7f61695..2ca9821 100644
--- a/content/test/data/accessibility/aria/aria-hidden-described-by-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-hidden-described-by-expected-blink.txt
@@ -2,10 +2,10 @@
 ++genericContainer ignored
 ++++genericContainer ignored
 ++++++genericContainer ignored
-++++++++genericContainer ignored invisible name='span-1'
-++++++++++genericContainer ignored invisible name='span-2'
-++++++++++genericContainer ignored invisible description='span-4' name='span-3' descriptionFrom=relatedElement describedbyIds=genericContainer
-++++++++++++genericContainer ignored invisible name='span-4'
+++++++++genericContainer ignored invisible
+++++++++++genericContainer ignored invisible
+++++++++++genericContainer ignored invisible
+++++++++++++genericContainer ignored invisible
 ++++++++genericContainer description='span-1' name='span-A' descriptionFrom=relatedElement describedbyIds=genericContainer
 ++++++++genericContainer description='span-2' name='span-B' descriptionFrom=relatedElement describedbyIds=genericContainer
 ++++++++genericContainer description='span-3' name='span-C' descriptionFrom=relatedElement describedbyIds=genericContainer
diff --git a/content/test/data/accessibility/aria/aria-hidden-described-by-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-hidden-described-by-expected-uia-win.txt
index ad28b7ec..78114b7 100644
--- a/content/test/data/accessibility/aria/aria-hidden-described-by-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-hidden-described-by-expected-uia-win.txt
@@ -1,5 +1,5 @@
 Document
-++Group Name='span-A' DescribedBy='span-1'
-++Group Name='span-B' DescribedBy='span-2'
-++Group Name='span-C' DescribedBy='span-3'
-++Group Name='span-D' DescribedBy='span-4'
+++Group Name='span-A' DescribedBy='{group}'
+++Group Name='span-B' DescribedBy='{group}'
+++Group Name='span-C' DescribedBy='{group}'
+++Group Name='span-D' DescribedBy='{group}'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-hidden-expected-blink.txt b/content/test/data/accessibility/aria/aria-hidden-expected-blink.txt
index 7f18ebd..ae1839a 100644
--- a/content/test/data/accessibility/aria/aria-hidden-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-hidden-expected-blink.txt
@@ -15,4 +15,4 @@
 ++++++genericContainer invisible name='blockDisplay Hiddentruefocusable' isLineBreakingObject=true
 ++++++++staticText ignored invisible name='blockDisplay Hiddentruefocusable'
 ++++++genericContainer ignored invisible
-++++++++heading invisible name='noneDisplayParent Hiddenfalse'
+++++++++heading invisible name='noneDisplayParent Hiddenfalse'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-hidden-labelled-by-expected-blink.txt b/content/test/data/accessibility/aria/aria-hidden-labelled-by-expected-blink.txt
index e648c00..62a98a72 100644
--- a/content/test/data/accessibility/aria/aria-hidden-labelled-by-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-hidden-labelled-by-expected-blink.txt
@@ -2,11 +2,11 @@
 ++genericContainer ignored
 ++++genericContainer ignored
 ++++++genericContainer ignored
-++++++++genericContainer ignored invisible name='span-1'
+++++++++genericContainer ignored invisible
 ++++++++genericContainer name='span-2'
 ++++++++genericContainer name='span-4' labelledbyIds=genericContainer
-++++++++++genericContainer ignored invisible name='span-4'
+++++++++++genericContainer ignored invisible
 ++++++++genericContainer name='span-1' labelledbyIds=genericContainer
 ++++++++genericContainer name='span-2' labelledbyIds=genericContainer
 ++++++++genericContainer name='span-3' labelledbyIds=genericContainer
-++++++++genericContainer name='span-4' labelledbyIds=genericContainer
+++++++++genericContainer name='span-4' labelledbyIds=genericContainer
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-hidden-single-descendant-expected-blink.txt b/content/test/data/accessibility/aria/aria-hidden-single-descendant-expected-blink.txt
index 351797b4..e8e5aa8 100644
--- a/content/test/data/accessibility/aria/aria-hidden-single-descendant-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-hidden-single-descendant-expected-blink.txt
@@ -8,4 +8,4 @@
 ++++++++button name='expect visible subtree'
 ++++++++++staticText name='expect visible subtree'
 ++++++++++++inlineTextBox name='expect visible subtree'
-++++++group name='Done'
+++++++group name='Done'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-hidden-single-descendant-visibility-hidden-expected-blink.txt b/content/test/data/accessibility/aria/aria-hidden-single-descendant-visibility-hidden-expected-blink.txt
index 2e7c237..c45ebcf 100644
--- a/content/test/data/accessibility/aria/aria-hidden-single-descendant-visibility-hidden-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-hidden-single-descendant-visibility-hidden-expected-blink.txt
@@ -3,10 +3,10 @@
 ++++genericContainer ignored
 ++++++genericContainer ignored invisible
 ++++++++genericContainer ignored invisible
-++++++++++button ignored invisible name='expect invisible subtree'
+++++++++++button ignored invisible
 ++++++++++++staticText ignored invisible name='expect invisible subtree'
 ++++++++genericContainer invisible
-++++++++++button ignored invisible name='expect invisible subtree'
+++++++++++button ignored invisible
 ++++++++genericContainer ignored invisible
 ++++++++++button invisible name='expect invisible subtree'
 ++++++++++++staticText ignored invisible name='expect invisible subtree'
diff --git a/content/test/data/accessibility/aria/aria-illegal-val-expected-blink.txt b/content/test/data/accessibility/aria/aria-illegal-val-expected-blink.txt
index c62d58df..ddb445b9 100644
--- a/content/test/data/accessibility/aria/aria-illegal-val-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-illegal-val-expected-blink.txt
@@ -25,4 +25,4 @@
 ++++++++treeItem name='Selected illegal' selected=true
 ++++++grid
 ++++++++columnHeader name='Sort illegal' sortDirection=other
-++++++genericContainer ignored invisible name='Hidden illegal'
+++++++genericContainer ignored invisible
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-labelledby-updates-expected-blink.txt b/content/test/data/accessibility/aria/aria-labelledby-updates-expected-blink.txt
index b185843..b94a197 100644
--- a/content/test/data/accessibility/aria/aria-labelledby-updates-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-labelledby-updates-expected-blink.txt
@@ -5,4 +5,4 @@
 ++++++paragraph ignored invisible
 ++++++++genericContainer ignored invisible
 ++++++++++genericContainer ignored invisible
-++++++++++++staticText ignored invisible name='oranges'
+++++++++++++staticText ignored invisible name='oranges'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-live-nested-expected-blink.txt b/content/test/data/accessibility/aria/aria-live-nested-expected-blink.txt
index dd4f3c4..beb9387 100644
--- a/content/test/data/accessibility/aria/aria-live-nested-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-live-nested-expected-blink.txt
@@ -10,7 +10,7 @@
 ++++++++++staticText containerLiveStatus='polite' name='Nested - polite'
 ++++++++++++inlineTextBox name='Nested - polite'
 ++++++group containerLiveStatus='polite' liveStatus='polite'
-++++++++genericContainer ignored containerLiveStatus='polite'
+++++++++genericContainer ignored
 ++++++++++group containerLiveStatus='assertive' liveStatus='assertive'
 ++++++++++++staticText containerLiveStatus='assertive' name='Nested - assertive'
-++++++++++++++inlineTextBox name='Nested - assertive'
+++++++++++++++inlineTextBox name='Nested - assertive'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-owns-crash-expected-blink.txt b/content/test/data/accessibility/aria/aria-owns-crash-expected-blink.txt
index 12e1ed6..b49c27c 100644
--- a/content/test/data/accessibility/aria/aria-owns-crash-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-owns-crash-expected-blink.txt
@@ -2,12 +2,12 @@
 ++genericContainer ignored
 ++++genericContainer ignored
 ++++++genericContainer ignored invisible
-++++++++genericContainer ignored invisible name='cats'
+++++++++genericContainer ignored invisible
 ++++++++++comboBoxGrouping ignored invisible
 ++++++++++++textField ignored invisible
 ++++++++++++++genericContainer ignored invisible
 ++++++++++++listBox ignored invisible
 ++++++++++++++listItem ignored invisible
-++++++++++++++++listMarker ignored invisible name='• '
+++++++++++++++++listMarker ignored invisible
 ++++++++++++++++++staticText ignored name='• '
 ++++++++genericContainer ignored invisible
diff --git a/content/test/data/accessibility/aria/hidden-described-by-expected-blink.txt b/content/test/data/accessibility/aria/hidden-described-by-expected-blink.txt
index 99841d5..cbef117 100644
--- a/content/test/data/accessibility/aria/hidden-described-by-expected-blink.txt
+++ b/content/test/data/accessibility/aria/hidden-described-by-expected-blink.txt
@@ -2,7 +2,7 @@
 ++genericContainer ignored
 ++++genericContainer ignored
 ++++++genericContainer ignored
-++++++++genericContainer ignored invisible name='span-A2'
+++++++++genericContainer ignored invisible
 ++++++++genericContainer description='span-A4' name='span-A3' descriptionFrom=relatedElement describedbyIds=genericContainer
 ++++++++++genericContainer ignored invisible
 ++++++++genericContainer description='span-A2' name='span-B' descriptionFrom=relatedElement describedbyIds=genericContainer
diff --git a/content/test/data/accessibility/aria/hidden-described-by-expected-uia-win.txt b/content/test/data/accessibility/aria/hidden-described-by-expected-uia-win.txt
index 87b945cf..ccbe0338 100644
--- a/content/test/data/accessibility/aria/hidden-described-by-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/hidden-described-by-expected-uia-win.txt
@@ -1,4 +1,4 @@
 Document
 ++Group Name='span-A3' DescribedBy='{group}'
-++Group Name='span-B' DescribedBy='span-A2'
+++Group Name='span-B' DescribedBy='{group}'
 ++Group Name='span-C'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/hidden-labelled-by-expected-blink.txt b/content/test/data/accessibility/aria/hidden-labelled-by-expected-blink.txt
index 6fb66ed..25381fc8 100644
--- a/content/test/data/accessibility/aria/hidden-labelled-by-expected-blink.txt
+++ b/content/test/data/accessibility/aria/hidden-labelled-by-expected-blink.txt
@@ -2,7 +2,7 @@
 ++genericContainer ignored
 ++++genericContainer ignored
 ++++++genericContainer ignored
-++++++++genericContainer ignored invisible name='span-A2'
+++++++++genericContainer ignored invisible
 ++++++++genericContainer name='span-A4' labelledbyIds=genericContainer
 ++++++++++genericContainer ignored invisible
 ++++++++genericContainer name='span-A2' labelledbyIds=genericContainer
diff --git a/content/test/data/accessibility/css/display-to-block-expected-blink.txt b/content/test/data/accessibility/css/display-to-block-expected-blink.txt
index 1ceff34..1837ddd 100644
--- a/content/test/data/accessibility/css/display-to-block-expected-blink.txt
+++ b/content/test/data/accessibility/css/display-to-block-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea
-++genericContainer ignored display='block'
-++++genericContainer ignored display='block'
-++++++genericContainer ignored display='block'
+++genericContainer ignored
+++++genericContainer ignored
+++++++genericContainer ignored
 ++++++++genericContainer display='block'
 ++++++++++staticText display='block' name='Cats'
 ++++++++++++inlineTextBox display='block' name='Cats'
@@ -9,4 +9,4 @@
 ++++++++++checkBox display='inline-block' name='checkbox' checkedState=false
 ++++++++genericContainer display='block'
 ++++++++++staticText display='inline' name='done'
-++++++++++++inlineTextBox display='inline' name='done'
+++++++++++++inlineTextBox display='inline' name='done'
\ No newline at end of file
diff --git a/content/test/data/accessibility/css/display-to-inline-expected-blink.txt b/content/test/data/accessibility/css/display-to-inline-expected-blink.txt
index 2e462d2..8591c34 100644
--- a/content/test/data/accessibility/css/display-to-inline-expected-blink.txt
+++ b/content/test/data/accessibility/css/display-to-inline-expected-blink.txt
@@ -1,11 +1,11 @@
 rootWebArea
-++genericContainer ignored display='block'
-++++genericContainer ignored display='block'
-++++++genericContainer ignored display='block'
+++genericContainer ignored
+++++genericContainer ignored
+++++++genericContainer ignored
 ++++++++staticText display='block' name='Cats '
 ++++++++++inlineTextBox display='block' name='Cats '
 ++++++++genericContainer display='inline'
 ++++++++++checkBox display='inline-block' name='checkbox' checkedState=false
 ++++++++genericContainer display='block'
 ++++++++++staticText display='inline' name='done'
-++++++++++++inlineTextBox display='inline' name='done'
+++++++++++++inlineTextBox display='inline' name='done'
\ No newline at end of file
diff --git a/content/test/data/accessibility/css/display-to-none-expected-blink.txt b/content/test/data/accessibility/css/display-to-none-expected-blink.txt
index 4172306..f0f9c3db 100644
--- a/content/test/data/accessibility/css/display-to-none-expected-blink.txt
+++ b/content/test/data/accessibility/css/display-to-none-expected-blink.txt
@@ -1,12 +1,12 @@
 rootWebArea
-++genericContainer ignored display='block'
-++++genericContainer ignored display='block'
-++++++genericContainer ignored display='block'
+++genericContainer ignored
+++++genericContainer ignored
+++++++genericContainer ignored
 ++++++++genericContainer display='block'
 ++++++++++staticText display='block' name='Cats'
 ++++++++++++inlineTextBox display='block' name='Cats'
-++++++++genericContainer ignored invisible display='none'
-++++++++++checkBox ignored invisible display='inline-block' name='checkbox' checkedState=false
+++++++++genericContainer ignored invisible
+++++++++++checkBox ignored invisible
 ++++++++genericContainer display='block'
 ++++++++++staticText display='inline' name='done'
 ++++++++++++inlineTextBox display='inline' name='done'
\ No newline at end of file
diff --git a/content/test/data/accessibility/css/visibility-to-hidden-expected-blink.txt b/content/test/data/accessibility/css/visibility-to-hidden-expected-blink.txt
index c27e4797..333c9b8 100644
--- a/content/test/data/accessibility/css/visibility-to-hidden-expected-blink.txt
+++ b/content/test/data/accessibility/css/visibility-to-hidden-expected-blink.txt
@@ -5,7 +5,7 @@
 ++++++++staticText name='Cats '
 ++++++++++inlineTextBox name='Cats '
 ++++++++genericContainer ignored invisible
-++++++++++checkBox ignored invisible name='checkbox' checkedState=false
+++++++++++checkBox ignored invisible
 ++++++++genericContainer
 ++++++++++staticText name='done'
-++++++++++++inlineTextBox name='done'
+++++++++++++inlineTextBox name='done'
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/add-alert-with-role-change-expected-mac.txt b/content/test/data/accessibility/event/add-alert-with-role-change-expected-mac.txt
new file mode 100644
index 0000000..d40a375
--- /dev/null
+++ b/content/test/data/accessibility/event/add-alert-with-role-change-expected-mac.txt
@@ -0,0 +1,2 @@
+AXLiveRegionChanged on AXGroup
+AXLiveRegionCreated on AXGroup
diff --git a/content/test/data/accessibility/event/add-alert-with-role-change-expected-uia-win.txt b/content/test/data/accessibility/event/add-alert-with-role-change-expected-uia-win.txt
new file mode 100644
index 0000000..18257334
--- /dev/null
+++ b/content/test/data/accessibility/event/add-alert-with-role-change-expected-uia-win.txt
@@ -0,0 +1 @@
+SystemAlert on role=alert
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/add-alert-with-role-change-expected-win.txt b/content/test/data/accessibility/event/add-alert-with-role-change-expected-win.txt
new file mode 100644
index 0000000..8f349d6
--- /dev/null
+++ b/content/test/data/accessibility/event/add-alert-with-role-change-expected-win.txt
@@ -0,0 +1 @@
+EVENT_SYSTEM_ALERT on <div#a> role=ROLE_SYSTEM_ALERT
diff --git a/content/test/data/accessibility/event/add-alert-with-role-change.html b/content/test/data/accessibility/event/add-alert-with-role-change.html
new file mode 100644
index 0000000..67eb622e
--- /dev/null
+++ b/content/test/data/accessibility/event/add-alert-with-role-change.html
@@ -0,0 +1,17 @@
+<!--
+@WIN-DENY:*
+@WIN-ALLOW:EVENT_SYSTEM_ALERT*
+@UIA-WIN-DENY:*
+@UIA-WIN-ALLOW:SystemAlert*
+-->
+<!DOCTYPE html>
+<html>
+<body>
+<div id="a">This is an alert</div>
+<script>
+  function go() {
+    document.getElementById('a').setAttribute('role', 'alert');
+}
+</script>
+</body>
+</html>
diff --git a/content/test/data/accessibility/event/aria-hidden-descendants-already-ignored-expected-win.txt b/content/test/data/accessibility/event/aria-hidden-descendants-already-ignored-expected-win.txt
index 0ac26d3f..957f2f7 100644
--- a/content/test/data/accessibility/event/aria-hidden-descendants-already-ignored-expected-win.txt
+++ b/content/test/data/accessibility/event/aria-hidden-descendants-already-ignored-expected-win.txt
@@ -1 +1 @@
-EVENT_OBJECT_HIDE on <div.a> role=ROLE_SYSTEM_GROUPING name="Heading" INVISIBLE
\ No newline at end of file
+EVENT_OBJECT_HIDE on <div.a> role=ROLE_SYSTEM_GROUPING INVISIBLE
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/aria-hidden-descendants-expected-win.txt b/content/test/data/accessibility/event/aria-hidden-descendants-expected-win.txt
index 7974253..de51619b 100644
--- a/content/test/data/accessibility/event/aria-hidden-descendants-expected-win.txt
+++ b/content/test/data/accessibility/event/aria-hidden-descendants-expected-win.txt
@@ -1,3 +1,3 @@
-EVENT_OBJECT_HIDE on <div.a> role=ROLE_SYSTEM_GROUPING name="Heading" INVISIBLE
+EVENT_OBJECT_HIDE on <div.a> role=ROLE_SYSTEM_GROUPING INVISIBLE
 EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL
 EVENT_OBJECT_SHOW on <div#banner-root.b> role=ROLE_SYSTEM_GROUPING name="Banner"
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/caret-move-hidden-input-expected-win.txt b/content/test/data/accessibility/event/caret-move-hidden-input-expected-win.txt
index e293d8a..3347eb4 100644
--- a/content/test/data/accessibility/event/caret-move-hidden-input-expected-win.txt
+++ b/content/test/data/accessibility/event/caret-move-hidden-input-expected-win.txt
@@ -1,4 +1,2 @@
 EVENT_OBJECT_FOCUS on <input#input> role=ROLE_SYSTEM_TEXT name="search" value="foo" FOCUSED,FOCUSABLE IA2_STATE_EDITABLE,IA2_STATE_SELECTABLE_TEXT,IA2_STATE_SINGLE_LINE
-EVENT_OBJECT_LOCATIONCHANGE role=ROLE_SYSTEM_CARET  window_class=Chrome_WidgetWin_0
-EVENT_OBJECT_SHOW role=ROLE_SYSTEM_CARET  window_class=Chrome_WidgetWin_0
-IA2_EVENT_TEXT_CARET_MOVED on <input#input> role=ROLE_SYSTEM_TEXT name="search" value="foo" FOCUSED,FOCUSABLE IA2_STATE_EDITABLE,IA2_STATE_SELECTABLE_TEXT,IA2_STATE_SINGLE_LINE
+IA2_EVENT_TEXT_CARET_MOVED on <input#input> role=ROLE_SYSTEM_TEXT name="search" value="foo" FOCUSED,FOCUSABLE IA2_STATE_EDITABLE,IA2_STATE_SELECTABLE_TEXT,IA2_STATE_SINGLE_LINE
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/expanded-changed-expected-uia-win.txt b/content/test/data/accessibility/event/expanded-changed-expected-uia-win.txt
index eb1be9ce..53e0dc7 100644
--- a/content/test/data/accessibility/event/expanded-changed-expected-uia-win.txt
+++ b/content/test/data/accessibility/event/expanded-changed-expected-uia-win.txt
@@ -1,3 +1,6 @@
 AriaProperties changed on role=link, name=Toggle
 AriaProperties changed on role=list, name=list
-ExpandCollapseExpandCollapseState changed on role=link, name=Toggle
+AriaProperties changed on role=listitem, name=list item 1
+AriaProperties changed on role=listitem, name=list item 2
+AriaProperties changed on role=listitem, name=list item 3
+ExpandCollapseExpandCollapseState changed on role=link, name=Toggle
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/action-verbs-expected-blink.txt b/content/test/data/accessibility/html/action-verbs-expected-blink.txt
index 1acfc908..a002168d 100644
--- a/content/test/data/accessibility/html/action-verbs-expected-blink.txt
+++ b/content/test/data/accessibility/html/action-verbs-expected-blink.txt
@@ -27,7 +27,7 @@
 ++++++details
 ++++++++genericContainer ignored
 ++++++++++disclosureTriangle collapsed name='Summary' defaultActionVerb=press
-++++++++++++listMarker ignored name='▸ '
+++++++++++++listMarker ignored
 ++++++++++++++staticText ignored name='▸ '
 ++++++++++++staticText name='Summary'
 ++++++++++++++inlineTextBox name='Summary'
diff --git a/content/test/data/accessibility/html/area-expected-blink.txt b/content/test/data/accessibility/html/area-expected-blink.txt
index 1ebad37..8316198 100644
--- a/content/test/data/accessibility/html/area-expected-blink.txt
+++ b/content/test/data/accessibility/html/area-expected-blink.txt
@@ -1,6 +1,6 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer
-++++++imageMap name='pipe'
+++++++image name='pipe'
 ++++++++link name='pipe1'
 ++++++++staticText name='pipe2'
diff --git a/content/test/data/accessibility/html/area-with-aria-owns-expected-blink.txt b/content/test/data/accessibility/html/area-with-aria-owns-expected-blink.txt
index 94e3b94f..ad8ae49 100644
--- a/content/test/data/accessibility/html/area-with-aria-owns-expected-blink.txt
+++ b/content/test/data/accessibility/html/area-with-aria-owns-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++imageMap name='pipe'
+++++++image name='pipe'
 ++++++++link name='pipe1'
 ++++++++link name='pipe1'
 ++++++paragraph
diff --git a/content/test/data/accessibility/html/body-tabindex-expected-blink.txt b/content/test/data/accessibility/html/body-tabindex-expected-blink.txt
index 42a04e7e..0794f7c 100644
--- a/content/test/data/accessibility/html/body-tabindex-expected-blink.txt
+++ b/content/test/data/accessibility/html/body-tabindex-expected-blink.txt
@@ -3,4 +3,4 @@
 ++++genericContainer ignored name='This test is for the body tag with a tabindex'
 ++++++paragraph
 ++++++++staticText name='This test is for the body tag with a tabindex'
-++++++++++inlineTextBox name='This test is for the body tag with a tabindex'
+++++++++++inlineTextBox name='This test is for the body tag with a tabindex'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/contenteditable-docs-li-disable-ng-layout-expected-blink.txt b/content/test/data/accessibility/html/contenteditable-docs-li-disable-ng-layout-expected-blink.txt
index 8713348..7e28d3d 100644
--- a/content/test/data/accessibility/html/contenteditable-docs-li-disable-ng-layout-expected-blink.txt
+++ b/content/test/data/accessibility/html/contenteditable-docs-li-disable-ng-layout-expected-blink.txt
@@ -82,16 +82,16 @@
 ++++++++++++presentational ignored isLineBreakingObject=true
 ++++++++++++++staticText name='Hello' nextOnLineId=inlineTextBox:" " previousOnLineId=listMarker:"9. "
 ++++++++++++++++inlineTextBox name='Hello' nextOnLineId=inlineTextBox:" " previousOnLineId=listMarker:"9. "
-++++++++++++++staticText name=' ' nextOnLineId=presentational:"<newline>" previousOnLineId=inlineTextBox:"Hello"
-++++++++++++++++inlineTextBox name=' ' nextOnLineId=presentational:"<newline>" previousOnLineId=inlineTextBox:"Hello"
-++++++++++++++presentational ignored name='<newline>' previousOnLineId=inlineTextBox:" " isLineBreakingObject=true
+++++++++++++++staticText name=' ' nextOnLineId=presentational previousOnLineId=inlineTextBox:"Hello"
+++++++++++++++++inlineTextBox name=' ' nextOnLineId=presentational previousOnLineId=inlineTextBox:"Hello"
+++++++++++++++presentational ignored isLineBreakingObject=true
 ++++++++++++++staticText name='world this' nextOnLineId=inlineTextBox:" "
 ++++++++++++++++inlineTextBox name='world this' nextOnLineId=inlineTextBox:" "
-++++++++++++++staticText name=' ' nextOnLineId=presentational:"<newline>" previousOnLineId=inlineTextBox:"world this"
-++++++++++++++++inlineTextBox name=' ' nextOnLineId=presentational:"<newline>" previousOnLineId=inlineTextBox:"world this"
-++++++++++++++presentational ignored name='<newline>' previousOnLineId=inlineTextBox:" " isLineBreakingObject=true
+++++++++++++++staticText name=' ' nextOnLineId=presentational previousOnLineId=inlineTextBox:"world this"
+++++++++++++++++inlineTextBox name=' ' nextOnLineId=presentational previousOnLineId=inlineTextBox:"world this"
+++++++++++++++presentational ignored isLineBreakingObject=true
 ++++++++++++++staticText name='is just like docs, and your kitchen sink'
 ++++++++++++++++inlineTextBox name='is just like docs, and your kitchen sink'
 ++++++++paragraph isLineBreakingObject=true
 ++++++++++staticText name='The end'
-++++++++++++inlineTextBox name='The end'
+++++++++++++inlineTextBox name='The end'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/contenteditable-docs-li-expected-blink.txt b/content/test/data/accessibility/html/contenteditable-docs-li-expected-blink.txt
index 101e6e6..96bf465 100644
--- a/content/test/data/accessibility/html/contenteditable-docs-li-expected-blink.txt
+++ b/content/test/data/accessibility/html/contenteditable-docs-li-expected-blink.txt
@@ -26,14 +26,14 @@
 ++++++++++++listMarker name='3. ' nextOnLineId=inlineTextBox:"A list item with an inner p role=presentation" isLineBreakingObject=true
 ++++++++++++++staticText name='3. ' nextOnLineId=inlineTextBox:"A list item with an inner p role=presentation"
 ++++++++++++++++inlineTextBox name='3. ' nextOnLineId=inlineTextBox:"A list item with an inner p role=presentation"
-++++++++++++presentational ignored previousOnLineId=inlineTextBox:"3. " isLineBreakingObject=true
+++++++++++++presentational ignored isLineBreakingObject=true
 ++++++++++++++staticText name='A list item with an inner p role=presentation' previousOnLineId=inlineTextBox:"3. "
 ++++++++++++++++inlineTextBox name='A list item with an inner p role=presentation' previousOnLineId=inlineTextBox:"3. "
 ++++++++++listItem isLineBreakingObject=true
 ++++++++++++listMarker name='4. ' nextOnLineId=inlineTextBox:"A list item with both a br and" isLineBreakingObject=true
 ++++++++++++++staticText name='4. ' nextOnLineId=inlineTextBox:"A list item with both a br and"
 ++++++++++++++++inlineTextBox name='4. ' nextOnLineId=inlineTextBox:"A list item with both a br and"
-++++++++++++presentational ignored previousOnLineId=inlineTextBox:"4. " isLineBreakingObject=true
+++++++++++++presentational ignored isLineBreakingObject=true
 ++++++++++++++staticText name='A list item with both a br and' nextOnLineId=inlineTextBox:"<newline>" previousOnLineId=inlineTextBox:"4. "
 ++++++++++++++++inlineTextBox name='A list item with both a br and' nextOnLineId=inlineTextBox:"<newline>" previousOnLineId=inlineTextBox:"4. "
 ++++++++++++++lineBreak name='<newline>' previousOnLineId=inlineTextBox:"A list item with both a br and" isLineBreakingObject=true
@@ -62,7 +62,7 @@
 ++++++++++++listMarker name='6. ' nextOnLineId=inlineTextBox:"A list item with a role presentation, and two inner spans, " isLineBreakingObject=true
 ++++++++++++++staticText name='6. ' nextOnLineId=inlineTextBox:"A list item with a role presentation, and two inner spans, "
 ++++++++++++++++inlineTextBox name='6. ' nextOnLineId=inlineTextBox:"A list item with a role presentation, and two inner spans, "
-++++++++++++presentational ignored previousOnLineId=inlineTextBox:"6. " isLineBreakingObject=true
+++++++++++++presentational ignored isLineBreakingObject=true
 ++++++++++++++staticText name='A list item with a role presentation, and two inner spans, ' nextOnLineId=inlineTextBox:"each separated by a space" previousOnLineId=inlineTextBox:"6. "
 ++++++++++++++++inlineTextBox name='A list item with a role presentation, and two inner spans, ' nextOnLineId=inlineTextBox:"each separated by a space" previousOnLineId=inlineTextBox:"6. "
 ++++++++++++++staticText name='each separated by a space' previousOnLineId=inlineTextBox:"A list item with a role presentation, and two inner spans, "
@@ -71,7 +71,7 @@
 ++++++++++++listMarker name='7. ' nextOnLineId=inlineTextBox:"A list item with a role presentation, and two inner spans," isLineBreakingObject=true
 ++++++++++++++staticText name='7. ' nextOnLineId=inlineTextBox:"A list item with a role presentation, and two inner spans,"
 ++++++++++++++++inlineTextBox name='7. ' nextOnLineId=inlineTextBox:"A list item with a role presentation, and two inner spans,"
-++++++++++++presentational ignored previousOnLineId=inlineTextBox:"7. " isLineBreakingObject=true
+++++++++++++presentational ignored isLineBreakingObject=true
 ++++++++++++++staticText name='A list item with a role presentation, and two inner spans,' nextOnLineId=inlineTextBox:"<newline>" previousOnLineId=inlineTextBox:"7. "
 ++++++++++++++++inlineTextBox name='A list item with a role presentation, and two inner spans,' nextOnLineId=inlineTextBox:"<newline>" previousOnLineId=inlineTextBox:"7. "
 ++++++++++++++lineBreak name='<newline>' previousOnLineId=inlineTextBox:"A list item with a role presentation, and two inner spans," isLineBreakingObject=true
@@ -82,7 +82,7 @@
 ++++++++++++listMarker name='8. ' nextOnLineId=inlineTextBox:"Hello" isLineBreakingObject=true
 ++++++++++++++staticText name='8. ' nextOnLineId=inlineTextBox:"Hello"
 ++++++++++++++++inlineTextBox name='8. ' nextOnLineId=inlineTextBox:"Hello"
-++++++++++++presentational ignored previousOnLineId=inlineTextBox:"8. " isLineBreakingObject=true
+++++++++++++presentational ignored isLineBreakingObject=true
 ++++++++++++++staticText name='Hello' nextOnLineId=inlineTextBox:" " previousOnLineId=inlineTextBox:"8. "
 ++++++++++++++++inlineTextBox name='Hello' nextOnLineId=inlineTextBox:" " previousOnLineId=inlineTextBox:"8. "
 ++++++++++++++staticText name=' ' nextOnLineId=inlineTextBox:"world this" previousOnLineId=inlineTextBox:"Hello"
@@ -97,15 +97,15 @@
 ++++++++++++listMarker name='9. ' nextOnLineId=inlineTextBox:"Hello" isLineBreakingObject=true
 ++++++++++++++staticText name='9. ' nextOnLineId=inlineTextBox:"Hello"
 ++++++++++++++++inlineTextBox name='9. ' nextOnLineId=inlineTextBox:"Hello"
-++++++++++++presentational ignored previousOnLineId=inlineTextBox:"9. " isLineBreakingObject=true
-++++++++++++++staticText name='Hello' nextOnLineId=presentational:"<newline>" previousOnLineId=inlineTextBox:"9. "
-++++++++++++++++inlineTextBox name='Hello' nextOnLineId=presentational:"<newline>" previousOnLineId=inlineTextBox:"9. "
+++++++++++++presentational ignored isLineBreakingObject=true
+++++++++++++++staticText name='Hello' nextOnLineId=presentational previousOnLineId=inlineTextBox:"9. "
+++++++++++++++++inlineTextBox name='Hello' nextOnLineId=presentational previousOnLineId=inlineTextBox:"9. "
 ++++++++++++++staticText name=' '
-++++++++++++++presentational ignored name='<newline>' previousOnLineId=inlineTextBox:"Hello" isLineBreakingObject=true
-++++++++++++++staticText name='world this' nextOnLineId=presentational:"<newline>"
-++++++++++++++++inlineTextBox name='world this' nextOnLineId=presentational:"<newline>"
+++++++++++++++presentational ignored isLineBreakingObject=true
+++++++++++++++staticText name='world this' nextOnLineId=presentational
+++++++++++++++++inlineTextBox name='world this' nextOnLineId=presentational
 ++++++++++++++staticText name=' '
-++++++++++++++presentational ignored name='<newline>' previousOnLineId=inlineTextBox:"world this" isLineBreakingObject=true
+++++++++++++++presentational ignored isLineBreakingObject=true
 ++++++++++++++staticText name='is just like docs, and your kitchen sink'
 ++++++++++++++++inlineTextBox name='is just like docs, and your kitchen sink'
 ++++++++paragraph isLineBreakingObject=true
diff --git a/content/test/data/accessibility/html/contenteditable-li-contains-presentation-expected-blink.txt b/content/test/data/accessibility/html/contenteditable-li-contains-presentation-expected-blink.txt
index 3535aa6..f920be9d 100644
--- a/content/test/data/accessibility/html/contenteditable-li-contains-presentation-expected-blink.txt
+++ b/content/test/data/accessibility/html/contenteditable-li-contains-presentation-expected-blink.txt
@@ -7,14 +7,14 @@
 ++++++++++++listMarker name='1. ' nextOnLineId=inlineTextBox:"text in span in presentational paragraph in li" isLineBreakingObject=true
 ++++++++++++++staticText name='1. ' nextOnLineId=inlineTextBox:"text in span in presentational paragraph in li"
 ++++++++++++++++inlineTextBox name='1. ' nextOnLineId=inlineTextBox:"text in span in presentational paragraph in li"
-++++++++++++presentational ignored previousOnLineId=inlineTextBox:"1. " isLineBreakingObject=true
+++++++++++++presentational ignored isLineBreakingObject=true
 ++++++++++++++staticText name='text in span in presentational paragraph in li' previousOnLineId=inlineTextBox:"1. "
 ++++++++++++++++inlineTextBox name='text in span in presentational paragraph in li' previousOnLineId=inlineTextBox:"1. "
 ++++++++++listItem isLineBreakingObject=true
 ++++++++++++listMarker name='2. ' nextOnLineId=inlineTextBox:"A list item with both a br and" isLineBreakingObject=true
 ++++++++++++++staticText name='2. ' nextOnLineId=inlineTextBox:"A list item with both a br and"
 ++++++++++++++++inlineTextBox name='2. ' nextOnLineId=inlineTextBox:"A list item with both a br and"
-++++++++++++presentational ignored previousOnLineId=inlineTextBox:"2. " isLineBreakingObject=true
+++++++++++++presentational ignored isLineBreakingObject=true
 ++++++++++++++staticText name='A list item with both a br and' nextOnLineId=inlineTextBox:"<newline>" previousOnLineId=inlineTextBox:"2. "
 ++++++++++++++++inlineTextBox name='A list item with both a br and' nextOnLineId=inlineTextBox:"<newline>" previousOnLineId=inlineTextBox:"2. "
 ++++++++++++++lineBreak name='<newline>' previousOnLineId=inlineTextBox:"A list item with both a br and" isLineBreakingObject=true
diff --git a/content/test/data/accessibility/html/continuations-expected-blink.txt b/content/test/data/accessibility/html/continuations-expected-blink.txt
index c02d8dc8..e862d93 100644
--- a/content/test/data/accessibility/html/continuations-expected-blink.txt
+++ b/content/test/data/accessibility/html/continuations-expected-blink.txt
@@ -1,6 +1,6 @@
 rootWebArea isLineBreakingObject=true
-++genericContainer ignored display='block' isLineBreakingObject=true
-++++genericContainer ignored display='block' isLineBreakingObject=true
+++genericContainer ignored isLineBreakingObject=true
+++++genericContainer ignored isLineBreakingObject=true
 ++++++group display='block' name='Group 1' isLineBreakingObject=true
 ++++++++genericContainer display='block' isLineBreakingObject=true
 ++++++++++button display='inline-block' name='Before' isLineBreakingObject=true
@@ -32,7 +32,7 @@
 ++++++++genericContainer display='block' isLineBreakingObject=true
 ++++++++++staticText display='block' name='Before'
 ++++++++++++inlineTextBox display='block' name='Before'
-++++++++genericContainer ignored display='block' isLineBreakingObject=true
+++++++++genericContainer ignored isLineBreakingObject=true
 ++++++++++paragraph display='block' isLineBreakingObject=true
 ++++++++++++staticText display='block' name='After'
 ++++++++++++++inlineTextBox display='block' name='After'
@@ -50,4 +50,4 @@
 ++++++++staticText display='inline' name='More italic and bold text'
 ++++++++++inlineTextBox display='inline' name='More italic and bold text'
 ++++++++staticText display='inline' name=' More italic text'
-++++++++++inlineTextBox display='inline' name=' More italic text'
+++++++++++inlineTextBox display='inline' name=' More italic text'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/details-expected-blink.txt b/content/test/data/accessibility/html/details-expected-blink.txt
index 1df79ec..3525e31 100644
--- a/content/test/data/accessibility/html/details-expected-blink.txt
+++ b/content/test/data/accessibility/html/details-expected-blink.txt
@@ -4,7 +4,7 @@
 ++++++details
 ++++++++genericContainer ignored
 ++++++++++disclosureTriangle collapsed name='details tag'
-++++++++++++listMarker ignored name='▸ '
+++++++++++++listMarker ignored
 ++++++++++++++staticText ignored name='▸ '
 ++++++++++++staticText name='details tag'
 ++++++++++++++inlineTextBox name='details tag'
@@ -13,7 +13,7 @@
 ++++++details
 ++++++++genericContainer ignored
 ++++++++++disclosureTriangle name='details tag open'
-++++++++++++listMarker ignored name='▾ '
+++++++++++++listMarker ignored
 ++++++++++++++staticText ignored name='▾ '
 ++++++++++++staticText name='details tag open'
 ++++++++++++++inlineTextBox name='details tag open'
diff --git a/content/test/data/accessibility/html/html-expected-blink.txt b/content/test/data/accessibility/html/html-expected-blink.txt
index c03d3baa..0348147 100644
--- a/content/test/data/accessibility/html/html-expected-blink.txt
+++ b/content/test/data/accessibility/html/html-expected-blink.txt
@@ -1,6 +1,6 @@
 rootWebArea htmlTag='#document' name='HTML element'
-++genericContainer ignored htmlTag='html' name='HTML element'
+++genericContainer ignored htmlTag='html'
 ++++genericContainer htmlTag='body' name='BODY element'
 ++++++button htmlTag='button' name='Button element'
 ++++++++staticText name='Button'
-++++++++++inlineTextBox name='Button'
+++++++++++inlineTextBox name='Button'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/img-empty-alt-expected-blink.txt b/content/test/data/accessibility/html/img-empty-alt-expected-blink.txt
index 10da993..e9df97c 100644
--- a/content/test/data/accessibility/html/img-empty-alt-expected-blink.txt
+++ b/content/test/data/accessibility/html/img-empty-alt-expected-blink.txt
@@ -1,9 +1,9 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer
-++++++presentational ignored name=''
+++++++presentational ignored
 ++++++image name=''
-++++++image ignored name=''
-++++++image ignored name=''
+++++++image ignored
+++++++image ignored
 ++++++image
-++++++image name='full'
+++++++image name='full'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/img-link-empty-alt-expected-blink.txt b/content/test/data/accessibility/html/img-link-empty-alt-expected-blink.txt
index a3eb2cb0..03626194 100644
--- a/content/test/data/accessibility/html/img-link-empty-alt-expected-blink.txt
+++ b/content/test/data/accessibility/html/img-link-empty-alt-expected-blink.txt
@@ -2,18 +2,18 @@
 ++genericContainer ignored
 ++++genericContainer
 ++++++link name='unread '
-++++++++presentational ignored name=''
+++++++++presentational ignored
 ++++++++staticText name='unread '
 ++++++++++inlineTextBox name='unread '
 ++++++link name='read '
-++++++++image ignored name=''
+++++++++image ignored
 ++++++++staticText name='read '
 ++++++++++inlineTextBox name='read '
 ++++++link name='read '
-++++++++image ignored name=''
+++++++++image ignored
 ++++++++staticText name='read '
 ++++++++++inlineTextBox name='read '
 ++++++link name='read'
 ++++++++image
 ++++++++staticText name='read'
-++++++++++inlineTextBox name='read'
+++++++++++inlineTextBox name='read'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/landmark-expected-blink.txt b/content/test/data/accessibility/html/landmark-expected-blink.txt
index 144fceb..1ce1196 100644
--- a/content/test/data/accessibility/html/landmark-expected-blink.txt
+++ b/content/test/data/accessibility/html/landmark-expected-blink.txt
@@ -69,7 +69,7 @@
 ++++++details
 ++++++++genericContainer ignored
 ++++++++++disclosureTriangle name='Details'
-++++++++++++listMarker ignored name='▸ '
+++++++++++++listMarker ignored
 ++++++++++++++staticText ignored name='▸ '
 ++++++++++++staticText name='Details'
 ++++++++++++++inlineTextBox name='Details'
@@ -122,7 +122,7 @@
 ++++++details
 ++++++++genericContainer ignored
 ++++++++++disclosureTriangle name='Details'
-++++++++++++listMarker ignored name='▸ '
+++++++++++++listMarker ignored
 ++++++++++++++staticText ignored name='▸ '
 ++++++++++++staticText name='Details'
 ++++++++++++++inlineTextBox name='Details'
@@ -174,7 +174,7 @@
 ++++++details
 ++++++++genericContainer ignored
 ++++++++++disclosureTriangle name='Details'
-++++++++++++listMarker ignored name='▸ '
+++++++++++++listMarker ignored
 ++++++++++++++staticText ignored name='▸ '
 ++++++++++++staticText name='Details'
 ++++++++++++++inlineTextBox name='Details'
@@ -227,7 +227,7 @@
 ++++++details
 ++++++++genericContainer ignored
 ++++++++++disclosureTriangle name='Details'
-++++++++++++listMarker ignored name='▸ '
+++++++++++++listMarker ignored
 ++++++++++++++staticText ignored name='▸ '
 ++++++++++++staticText name='Details'
 ++++++++++++++inlineTextBox name='Details'
diff --git a/content/test/data/accessibility/html/li-expected-blink.txt b/content/test/data/accessibility/html/li-expected-blink.txt
index 208efcc..c2f158a 100644
--- a/content/test/data/accessibility/html/li-expected-blink.txt
+++ b/content/test/data/accessibility/html/li-expected-blink.txt
@@ -1,6 +1,6 @@
 rootWebArea
-++genericContainer ignored display='block'
-++++genericContainer ignored display='block'
+++genericContainer ignored
+++++genericContainer ignored
 ++++++list display='block' setSize=3
 ++++++++listItem display='list-item' name='Custom name' hierarchicalLevel=1 setSize=3 posInSet=1
 ++++++++++listMarker display='inline-block' name='• '
diff --git a/content/test/data/accessibility/html/map-with-aria-owns-expected-blink.txt b/content/test/data/accessibility/html/map-with-aria-owns-expected-blink.txt
index 11bd671..bdf13b96 100644
--- a/content/test/data/accessibility/html/map-with-aria-owns-expected-blink.txt
+++ b/content/test/data/accessibility/html/map-with-aria-owns-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea name='done'
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++imageMap name='pipe'
+++++++image name='pipe'
 ++++++++link name='pipe1'
 ++++++genericContainer
 ++++++paragraph ignored
diff --git a/content/test/data/accessibility/html/map-with-role-expected-blink.txt b/content/test/data/accessibility/html/map-with-role-expected-blink.txt
index a9b2afe..e9dd9cdd6 100644
--- a/content/test/data/accessibility/html/map-with-role-expected-blink.txt
+++ b/content/test/data/accessibility/html/map-with-role-expected-blink.txt
@@ -1,5 +1,5 @@
 rootWebArea name='done'
 ++genericContainer ignored
 ++++genericContainer
-++++++imageMap name='pipe'
+++++++image name='pipe'
 ++++++++link name='pipe1'
diff --git a/content/test/data/accessibility/html/modal-dialog-opened-expected-blink.txt b/content/test/data/accessibility/html/modal-dialog-opened-expected-blink.txt
index f8247b0..eaae2bf 100644
--- a/content/test/data/accessibility/html/modal-dialog-opened-expected-blink.txt
+++ b/content/test/data/accessibility/html/modal-dialog-opened-expected-blink.txt
@@ -10,8 +10,8 @@
 ++++++++++++++inlineTextBox name='Link inside the dialog.'
 ++++++++popUpButton ignored invisible
 ++++++++++menuListPopup ignored invisible
-++++++button ignored invisible name='No file chosen, Choose File'
-++++++++button ignored invisible name='Choose File'
+++++++button ignored invisible
+++++++++button ignored invisible
 ++++++++genericContainer ignored invisible
 ++++++++++staticText ignored invisible name='No file chosen'
 ++++++dialog ignored invisible
diff --git a/content/test/data/accessibility/html/modal-dialog-stack-expected-blink.txt b/content/test/data/accessibility/html/modal-dialog-stack-expected-blink.txt
index e577fbc..b4c2523 100644
--- a/content/test/data/accessibility/html/modal-dialog-stack-expected-blink.txt
+++ b/content/test/data/accessibility/html/modal-dialog-stack-expected-blink.txt
@@ -5,7 +5,7 @@
 ++++++++dialog ignored invisible
 ++++++++popUpButton ignored invisible
 ++++++++++menuListPopup ignored invisible
-++++++button ignored invisible name='This button should not be in the tree.'
+++++++button ignored invisible
 ++++++dialog ignored invisible
 ++++++dialog
 ++++++++staticText name='This is the now active dialog. Of course it should be in the tree. '
diff --git a/content/test/data/accessibility/html/relevant-space-expected-blink.txt b/content/test/data/accessibility/html/relevant-space-expected-blink.txt
index 6dba043..0d4de42b 100644
--- a/content/test/data/accessibility/html/relevant-space-expected-blink.txt
+++ b/content/test/data/accessibility/html/relevant-space-expected-blink.txt
@@ -19,6 +19,6 @@
 ++++++++staticText name='E('
 ++++++++++inlineTextBox name='E('
 ++++++++staticText name=' '
-++++++++presentational ignored name='<newline>'
+++++++++presentational ignored
 ++++++++staticText name=')F'
-++++++++++inlineTextBox name=')F'
+++++++++++inlineTextBox name=')F'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/ruby-expected-blink.txt b/content/test/data/accessibility/html/ruby-expected-blink.txt
index 1e35d12..c86da7f 100644
--- a/content/test/data/accessibility/html/ruby-expected-blink.txt
+++ b/content/test/data/accessibility/html/ruby-expected-blink.txt
@@ -4,16 +4,16 @@
 ++++++ruby description='ruby text' descriptionFrom=relatedElement
 ++++++++staticText name='ruby base'
 ++++++++++inlineTextBox name='ruby base'
-++++++++rubyAnnotation ignored name='ruby text'
+++++++++rubyAnnotation ignored
 ++++++paragraph
 ++++++++ruby description='ruby text' descriptionFrom=relatedElement
 ++++++++++staticText name='ruby base'
 ++++++++++++inlineTextBox name='ruby base'
-++++++++++rubyAnnotation ignored name='ruby text'
+++++++++++rubyAnnotation ignored
 ++++++paragraph
 ++++++++staticText name='Hi! '
 ++++++++++inlineTextBox name='Hi! '
 ++++++++ruby description='ruby text' descriptionFrom=relatedElement
 ++++++++++staticText name='ruby base'
 ++++++++++++inlineTextBox name='ruby base'
-++++++++++rubyAnnotation ignored name='ruby text'
\ No newline at end of file
+++++++++++rubyAnnotation ignored
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/summary-expected-blink.txt b/content/test/data/accessibility/html/summary-expected-blink.txt
index 38008f8..40029320 100644
--- a/content/test/data/accessibility/html/summary-expected-blink.txt
+++ b/content/test/data/accessibility/html/summary-expected-blink.txt
@@ -4,7 +4,7 @@
 ++++++details
 ++++++++genericContainer ignored
 ++++++++++disclosureTriangle collapsed name='details tag'
-++++++++++++listMarker ignored name='▸ '
+++++++++++++listMarker ignored
 ++++++++++++++staticText ignored name='▸ '
 ++++++++++++staticText name='details tag'
 ++++++++++++++inlineTextBox name='details tag'
diff --git a/content/test/data/accessibility/html/video-text-only-expected-auralinux.txt b/content/test/data/accessibility/html/video-text-only-expected-auralinux.txt
index c0b860f6..a2d1765f 100644
--- a/content/test/data/accessibility/html/video-text-only-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/video-text-only-expected-auralinux.txt
@@ -1,3 +1,5 @@
 [document web]
 ++[section]
+++++[video] name='Unable to play media.'
 ++++[static] name=' '
+++++[video] name='Unable to play media.'
diff --git a/content/test/data/accessibility/html/video-text-only-expected-blink.txt b/content/test/data/accessibility/html/video-text-only-expected-blink.txt
index e3423ee..74203b24 100644
--- a/content/test/data/accessibility/html/video-text-only-expected-blink.txt
+++ b/content/test/data/accessibility/html/video-text-only-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer
-++++++video ignored name='Unable to play media.'
+++++++video name='Unable to play media.' restriction=disabled
 ++++++staticText name=' '
 ++++++++inlineTextBox name=' '
-++++++video ignored name='Unable to play media.'
\ No newline at end of file
+++++++video name='Unable to play media.' restriction=disabled
diff --git a/content/test/data/accessibility/regression/reused-map-change-map-name-expected-blink.txt b/content/test/data/accessibility/regression/reused-map-change-map-name-expected-blink.txt
index 3a5ba48..0903328 100644
--- a/content/test/data/accessibility/regression/reused-map-change-map-name-expected-blink.txt
+++ b/content/test/data/accessibility/regression/reused-map-change-map-name-expected-blink.txt
@@ -6,7 +6,7 @@
 ++++++++inlineTextBox name=' '
 ++++++image name='star2'
 ++++++staticText name=' '
-++++++imageMap name='star3'
+++++++image name='star3'
 ++++++++link focusable name='Area'
 ++++++++link focusable name='Area2'
 ++++++staticText name=' '
diff --git a/content/test/data/accessibility/regression/reused-map-change-usemap-expected-blink.txt b/content/test/data/accessibility/regression/reused-map-change-usemap-expected-blink.txt
index bd916d7..63e08ede 100644
--- a/content/test/data/accessibility/regression/reused-map-change-usemap-expected-blink.txt
+++ b/content/test/data/accessibility/regression/reused-map-change-usemap-expected-blink.txt
@@ -4,12 +4,12 @@
 ++++++image name='star1'
 ++++++staticText name=' '
 ++++++++inlineTextBox name=' '
-++++++imageMap name='star2'
+++++++image name='star2'
 ++++++++link focusable name='Area'
 ++++++staticText name=' '
 ++++++image name='star3'
 ++++++staticText name=' '
 ++++++++inlineTextBox name=' '
-++++++imageMap name='star4'
+++++++image name='star4'
 ++++++++link focusable name='Area'
 ++++++++link focusable name='Area2'
diff --git a/content/test/data/accessibility/regression/reused-map-expected-blink.txt b/content/test/data/accessibility/regression/reused-map-expected-blink.txt
index 940a7f7..039a0e2 100644
--- a/content/test/data/accessibility/regression/reused-map-expected-blink.txt
+++ b/content/test/data/accessibility/regression/reused-map-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea focusable name='done'
 ++genericContainer ignored
 ++++genericContainer
-++++++imageMap name='star1'
+++++++image name='star1'
 ++++++++link focusable name='Area2'
 ++++++staticText name=' '
 ++++++++inlineTextBox name=' '
diff --git a/content/test/data/accessibility/regression/reused-map-move-image-expected-blink.txt b/content/test/data/accessibility/regression/reused-map-move-image-expected-blink.txt
index 92395f3..31090283 100644
--- a/content/test/data/accessibility/regression/reused-map-move-image-expected-blink.txt
+++ b/content/test/data/accessibility/regression/reused-map-move-image-expected-blink.txt
@@ -1,9 +1,9 @@
 rootWebArea focusable name='done'
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++imageMap name='star0'
+++++++image name='star0'
 ++++++++link focusable name='Area'
-++++++imageMap name='star2'
+++++++image name='star2'
 ++++++++link focusable name='Area'
 ++++++++link focusable name='Area2'
 ++++++genericContainer
diff --git a/content/test/data/accessibility/regression/reused-map-move-image-to-top-expected-blink.txt b/content/test/data/accessibility/regression/reused-map-move-image-to-top-expected-blink.txt
index 9d870f56..690ebf1 100644
--- a/content/test/data/accessibility/regression/reused-map-move-image-to-top-expected-blink.txt
+++ b/content/test/data/accessibility/regression/reused-map-move-image-to-top-expected-blink.txt
@@ -1,11 +1,11 @@
 rootWebArea focusable name='done'
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++imageMap name='star0'
+++++++image name='star0'
 ++++++++link focusable name='Area'
 ++++++staticText name=' '
 ++++++++inlineTextBox name=' '
-++++++imageMap name='star1'
+++++++image name='star1'
 ++++++++link focusable name='Area'
 ++++++++link focusable name='Area2'
 ++++++image name='star2'
diff --git a/content/web_test/browser/web_test_control_host.cc b/content/web_test/browser/web_test_control_host.cc
index bce6a6b..f51e10d 100644
--- a/content/web_test/browser/web_test_control_host.cc
+++ b/content/web_test/browser/web_test_control_host.cc
@@ -712,8 +712,7 @@
     StoragePartition* storage_partition =
         BrowserContext::GetStoragePartition(browser_context, nullptr);
     storage_partition->GetCookieManagerForBrowserProcess()->DeleteCookies(
-        network::mojom::CookieDeletionFilter::New(),
-        base::BindOnce([](uint32_t) {}));
+        network::mojom::CookieDeletionFilter::New(), base::DoNothing());
   }
 
   ui::SelectFileDialog::SetFactory(nullptr);
diff --git a/device/bluetooth/strings/BUILD.gn b/device/bluetooth/strings/BUILD.gn
index ae78738..952d9c6 100644
--- a/device/bluetooth/strings/BUILD.gn
+++ b/device/bluetooth/strings/BUILD.gn
@@ -10,7 +10,7 @@
   source = "../bluetooth_strings.grd"
   outputs =
       [ "grit/bluetooth_strings.h" ] +
-      process_file_template(locales_with_fake_bidi,
+      process_file_template(locales_with_pseudolocales,
                             [ "bluetooth_strings_{{source_name_part}}.pak" ])
 }
 
diff --git a/device/fido/strings/BUILD.gn b/device/fido/strings/BUILD.gn
index c14db17..173defb 100644
--- a/device/fido/strings/BUILD.gn
+++ b/device/fido/strings/BUILD.gn
@@ -8,6 +8,6 @@
 grit("strings") {
   source = "../fido_strings.grd"
   outputs = [ "grit/fido_strings.h" ] +
-            process_file_template(locales_with_fake_bidi,
+            process_file_template(locales_with_pseudolocales,
                                   [ "fido_strings_{{source_name_part}}.pak" ])
 }
diff --git a/docs/chromeos_build_instructions.md b/docs/chromeos_build_instructions.md
index af8aed0..ec41f84 100644
--- a/docs/chromeos_build_instructions.md
+++ b/docs/chromeos_build_instructions.md
@@ -70,7 +70,6 @@
     is_debug = false           # Release build, runs faster.
     dcheck_always_on = true    # Enables DCHECK despite release build.
     enable_nacl = false        # Skips native client build, compiles faster.
-    use_sysroot = false        # Build for local machine instead of sysroot.
 
     # Set the following true to create a Chrome (instead of Chromium) build.
     # This requires a src-internal checkout.
diff --git a/docs/gpu/vaapi.md b/docs/gpu/vaapi.md
index 4b8e44c..a81787a 100644
--- a/docs/gpu/vaapi.md
+++ b/docs/gpu/vaapi.md
@@ -204,7 +204,8 @@
 correctly; try running `vainfo` from the command line and verify no errors show
 up.
 
-To run Chromium using VaAPI two arguments are necessary:
+To run Chromium using VaAPI three arguments are necessary:
+* `--enable-features=VaapiVideoDecoder`
 * `--ignore-gpu-blocklist`
 * `--use-gl=desktop` or `--use-gl=egl`
 
diff --git a/docs/speed/metrics_changelog/2021_03_cls.md b/docs/speed/metrics_changelog/2021_03_cls.md
new file mode 100644
index 0000000..2fde478
--- /dev/null
+++ b/docs/speed/metrics_changelog/2021_03_cls.md
@@ -0,0 +1,9 @@
+# Cumulative Layout Shift Changes in Chrome 91
+
+### Improvement for shift with counterscroll
+
+[Source code](https://chromium-review.googlesource.com/c/chromium/src/+/2741240)
+
+## When were users affected?
+
+Chrome 91 is currently scheduled to be released the week of May 25, 2021.
diff --git a/docs/speed/metrics_changelog/cls.md b/docs/speed/metrics_changelog/cls.md
index 146d11d3..098c590 100644
--- a/docs/speed/metrics_changelog/cls.md
+++ b/docs/speed/metrics_changelog/cls.md
@@ -2,6 +2,8 @@
 
 This is a list of changes to [Cumulative Layout Shift](https://web.dev/cls).
 
+* Chrome 91
+  * Metric definition improvement: [Improvement for shift with counterscroll](2021_03_cls.md)
 * Chrome 90
   * Metric definition improvement: [Bug fixes involving changes to transform, effect, clip or position](2021_02_cls.md)
   * Metric definition improvement: [Consider transform change countering layout shift](2021_02_cls.md)
diff --git a/docs/ui/index.md b/docs/ui/index.md
index 3aee457c..f48167b 100644
--- a/docs/ui/index.md
+++ b/docs/ui/index.md
@@ -19,6 +19,7 @@
 * [Views](/docs/ui/views/overview.md)
 * [Platform Styling](/docs/ui/views/platform_style.md)
 * [Product Excellence](/docs/ui/product_excellence/index.md)
+* [UI Debugging](/docs/ui/learn/ui_debugging.md)
 * [UI Devtools](/docs/ui/ui_devtools/index.md)
 * [Input Event Routing](/docs/ui/input_event/index.md)
 * [Metadata and Properties](/docs/ui/views/metadata_properties.md)
diff --git a/docs/ui/learn/index.md b/docs/ui/learn/index.md
index 206cfa5a..8e7413f 100644
--- a/docs/ui/learn/index.md
+++ b/docs/ui/learn/index.md
@@ -10,6 +10,7 @@
 
 * [Views](/docs/ui/views/overview.md)
 * [Product Excellence](/docs/ui/product_excellence/index.md)
+* [UI Debugging](/docs/ui/learn/ui_debugging.md)
 * [UI Devtools](/docs/ui/ui_devtools/index.md)
 * [Input Event Routing](/docs/ui/input_event/index.md)
 * [Metadata and Properties](/docs/ui/views/metadata_properties.md)
diff --git a/docs/ui/learn/ui_debugging.md b/docs/ui/learn/ui_debugging.md
new file mode 100644
index 0000000..a36787b
--- /dev/null
+++ b/docs/ui/learn/ui_debugging.md
@@ -0,0 +1,82 @@
+# Chromium Desktop UI Debugging Tools and Tips
+
+To help develop and debug Chromium desktop UI, this doc shares a set of
+developer tools and their usages.
+
+
+## UI Debugging Shortcuts
+
+After enabling `ui-debug-tools` flag from `chrome://flags`, developers will be
+able to use the following keyboard shortcuts:
+
+| Actions             | Shortcuts |
+|---------------------|:--------- |
+| Ctrl+Alt+Shift+T    | Toggle between non-Tablet mode and Tablet mode |
+| Ctrl+Alt+Shift+V    | Print out the current Views tree hierarchy     |
+| Ctrl+Alt+Shift+M    | Print out all the views on the Views tree with their properties in details |
+
+
+## UI DevTools
+
+UI DevTools is a set of graphical inspection and debugging tools for Chromium
+native UI. They largely resemble the Web DevTools used by Web developers.
+There are two ways to enable the tools:
+* Enable `ui-debug-tools` flag from `chrome://flags`
+* Execute chromium/chrome with `--enable-ui-devtools` command line flag
+
+Once being enabled, UI DevTools can be launched through a button on
+`chrome://inspect#native-ui` page. Thus, UI developers are able to
+examine the UI structure, individual view's layout and properties etc.
+
+Detailed usage information and feature tutorials can be found in this [doc](https://chromium.googlesource.com/chromium/src/+/master/docs/ui/ui_devtools/index.md).
+
+
+
+## Debugger Extensions/Scripts
+
+For developers who prefer to use debuggers, some debugger extensions or
+scripts can be helpful. Once being loaded into the debugger, they would provide
+custom commands such as printing out a view's properties or the view hierarchy
+information.
+
+
+| Target Debugger       | Extension/Script Usage | Code Location|
+|-----------------------|:-----------------------|--------------|
+| Windbg                | [README][]             | [Source][]   |
+| LLDB                  | TBA                    | TBA          |
+
+[README]: https://chromium.googlesource.com/chromium/src/+/master/tools/win/chromeexts/README.md
+[Source]: https://source.chromium.org/chromium/chromium/src/+/master:tools/win/chromeexts/
+
+
+## Views examples
+
+Ever wonder what common controls the Views framework provides? And how to
+customize them?
+
+`views_examples` and `views_examples_with_content` are two programs that list
+those controls and show examples on how to use them. Just build and run the
+following commands:
+
+```shell
+$ autoninja -C out/Default views_examples
+
+$ out/Default/views_examples [--enable-examples=<example1,[example2...]>]
+```
+
+The list of all available examples can be found in
+[README](https://chromium.googlesource.com/chromium/src/+/master/ui/views/examples/README.md).
+
+
+## Note
+
+### Adding Metadata and Properties
+
+To be able to inspect the properties of a view through those tools, the
+corresponding view needs to have metadata and properties built in it. This
+requires adding some standard macros. A simple example can be found from this
+[comment](https://source.chromium.org/chromium/chromium/src/+/master:ui/views/view.h?q=%22Property%20metadata%22&ss=chromium%2Fchromium%2Fsrc).
+
+More advanced usages of the macros and special property handlings are elaborated
+in this [doc](https://chromium.googlesource.com/chromium/src/+/master/docs/ui/views/metadata_properties.md).
+
diff --git a/docs/ui/ui_devtools/images/launch_tools.png b/docs/ui/ui_devtools/images/launch_tools.png
new file mode 100644
index 0000000..497de01
--- /dev/null
+++ b/docs/ui/ui_devtools/images/launch_tools.png
Binary files differ
diff --git a/docs/ui/ui_devtools/index.md b/docs/ui/ui_devtools/index.md
index e56dde4..3fd86bd 100644
--- a/docs/ui/ui_devtools/index.md
+++ b/docs/ui/ui_devtools/index.md
@@ -1,21 +1,30 @@
 # UI DevTools Overview
 
-UI DevTools allows UI developers to inspect the Chrome desktop UI system like a webpage using the frontend DevTools Inspector. It is
-currently supported on Linux, Windows, Mac, and ChromeOS.
-
-* [Old Ash Doc](https://www.chromium.org/developers/how-tos/inspecting-ash)
-* [Backend Source Code](https://cs.chromium.org/chromium/src/components/ui_devtools/)
-* [Inspector Frontend Source Code](https://chromium.googlesource.com/devtools/devtools-frontend)
+UI DevTools allows UI developers to inspect the Chrome desktop UI system like
+a webpage using the frontend DevTools Inspector. It is
+currently supported on all desktop platforms including Linux, Windows, Mac,
+and ChromeOS.
 
 ## How to run
 
-1. Run Chromium with default port 9223 and start Chromium with UI DevTools flag:
+There are two ways to enable UI DevTools:
 
-        $ out/Default/chrome --enable-ui-devtools
+1. Run Chromium with default port 9223 and start Chromium with UI DevTools
+   command line flag:
 
-    * If you want to use different port, add port in the flag `--enable-ui-devtools=<port>`.
-2. In the Chrome Omnibox, go to chrome://inspect#other and click `inspect` under UIDevToolsClient.
-    * Direct link is devtools://devtools/bundled/inspector.html?ws=localhost:9223/0.
+   ```shell
+   $ out/Default/chrome --enable-ui-devtools
+   ```
+
+    * If you want to use a different port, add the port number in the flag
+    `--enable-ui-devtools=<port>`.
+
+2. Enable `ui-debug-tools` feature flag from `chrome://flags`
+
+Once enabled, go to `chrome://inspect#native-ui` and click the `Inspect Native UI`
+button to launch the DevTools front-end in a separate tab.
+
+![launch UIDevTools]
 
 
 ## How to Use
@@ -112,6 +121,7 @@
 
 ![search style]
 
+[launch UIDevTools]: images/launch_tools.png
 [expand elements]: images/expand_elements.gif
 [browser frame properties]: images/browser_frame_properties.png
 [image view properties]: images/image_view_properties.png
@@ -126,3 +136,9 @@
 [lock and inspect bubble]: images/lock_and_inspect_bubble.gif
 [ui element tree search]: images/ui_element_tree_search.gif
 [search style]: images/search_style.png
+
+### References
+
+* [Old Ash Doc](https://www.chromium.org/developers/how-tos/inspecting-ash) (obsolete)
+* [Backend Source Code](https://cs.chromium.org/chromium/src/components/ui_devtools/)
+* [Inspector Frontend Source Code](https://chromium.googlesource.com/devtools/devtools-frontend)
diff --git a/extensions/browser/extension_navigation_throttle.cc b/extensions/browser/extension_navigation_throttle.cc
index e327235b..250d0b77 100644
--- a/extensions/browser/extension_navigation_throttle.cc
+++ b/extensions/browser/extension_navigation_throttle.cc
@@ -210,10 +210,16 @@
           registry->enabled_extensions().GetByID(owner_extension_id);
 
       content::StoragePartitionConfig storage_partition_config =
-          content::StoragePartitionConfig::CreateDefault();
-      bool is_guest = WebViewGuest::GetGuestPartitionConfigForSite(
-          navigation_handle()->GetStartingSiteInstance()->GetSiteURL(),
-          &storage_partition_config);
+          content::StoragePartitionConfig::CreateDefault(browser_context);
+      bool is_guest = navigation_handle()->GetStartingSiteInstance()->IsGuest();
+      if (is_guest) {
+        is_guest = WebViewGuest::GetGuestPartitionConfigForSite(
+            browser_context,
+            navigation_handle()->GetStartingSiteInstance()->GetSiteURL(),
+            &storage_partition_config);
+      }
+      CHECK_EQ(is_guest,
+               navigation_handle()->GetStartingSiteInstance()->IsGuest());
 
       bool allowed = true;
       url_request_util::AllowCrossRendererResourceLoadHelper(
diff --git a/extensions/browser/extension_util.cc b/extensions/browser/extension_util.cc
index f993a955..3626e4d 100644
--- a/extensions/browser/extension_util.cc
+++ b/extensions/browser/extension_util.cc
@@ -125,10 +125,11 @@
     // the |partition_domain|. The |in_memory| and |partition_name| are only
     // used in guest schemes so they are cleared here.
     return content::StoragePartitionConfig::Create(
-        extension_id, std::string() /* partition_name */, false /*in_memory */);
+        browser_context, extension_id, std::string() /* partition_name */,
+        false /*in_memory */);
   }
 
-  return content::StoragePartitionConfig::CreateDefault();
+  return content::StoragePartitionConfig::CreateDefault(browser_context);
 }
 
 content::StoragePartition* GetStoragePartitionForExtensionId(
diff --git a/extensions/browser/guest_view/web_view/web_view_guest.cc b/extensions/browser/guest_view/web_view/web_view_guest.cc
index b6da40b2..9d514c5 100644
--- a/extensions/browser/guest_view/web_view/web_view_guest.cc
+++ b/extensions/browser/guest_view/web_view/web_view_guest.cc
@@ -272,6 +272,7 @@
 
 // static
 bool WebViewGuest::GetGuestPartitionConfigForSite(
+    content::BrowserContext* browser_context,
     const GURL& site,
     content::StoragePartitionConfig* storage_partition_config) {
   if (!site.SchemeIs(content::kGuestScheme))
@@ -296,7 +297,7 @@
   bool in_memory = (site.path() != "/persist");
 
   *storage_partition_config = content::StoragePartitionConfig::Create(
-      site.host(), partition_name, in_memory);
+      browser_context, site.host(), partition_name, in_memory);
   // A <webview> inside a chrome app needs to be able to resolve Blob URLs that
   // were created by the chrome app. The chrome app has the same
   // partition_domain but empty partition_name. Setting this flag on the
@@ -390,6 +391,8 @@
                                      WebContentsCreatedCallback callback) {
   RenderProcessHost* owner_render_process_host =
       owner_web_contents()->GetMainFrame()->GetProcess();
+  DCHECK_EQ(browser_context(), owner_render_process_host->GetBrowserContext());
+
   std::string storage_partition_id;
   bool persist_storage = false;
   ParsePartitionParam(create_params, &storage_partition_id, &persist_storage);
@@ -405,15 +408,15 @@
   }
   std::string partition_domain = GetOwnerSiteURL().host();
   auto partition_config = content::StoragePartitionConfig::Create(
-      partition_domain, storage_partition_id, !persist_storage /* in_memory */);
+      browser_context(), partition_domain, storage_partition_id,
+      !persist_storage /* in_memory */);
 
   if (GetOwnerSiteURL().SchemeIs(extensions::kExtensionScheme)) {
     auto owner_config =
         extensions::util::GetStoragePartitionConfigForExtensionId(
-            GetOwnerSiteURL().host(),
-            owner_render_process_host->GetBrowserContext());
-    if (owner_render_process_host->GetBrowserContext()->IsOffTheRecord()) {
-      owner_config = owner_config.CopyWithInMemorySet();
+            GetOwnerSiteURL().host(), browser_context());
+    if (browser_context()->IsOffTheRecord()) {
+      DCHECK(owner_config.in_memory());
     }
     if (!owner_config.is_default()) {
       partition_config.set_fallback_to_partition_domain_for_blob_urls(
@@ -431,20 +434,19 @@
   // If we already have a webview tag in the same app using the same storage
   // partition, we should use the same SiteInstance so the existing tag and
   // the new tag can script each other.
-  auto* guest_view_manager = GuestViewManager::FromBrowserContext(
-      owner_render_process_host->GetBrowserContext());
+  auto* guest_view_manager =
+      GuestViewManager::FromBrowserContext(browser_context());
   scoped_refptr<content::SiteInstance> guest_site_instance =
       guest_view_manager->GetGuestSiteInstance(guest_site);
   if (!guest_site_instance) {
     // Create the SiteInstance in a new BrowsingInstance, which will ensure
     // that webview tags are also not allowed to send messages across
     // different partitions.
-    guest_site_instance = content::SiteInstance::CreateForGuest(
-        owner_render_process_host->GetBrowserContext(), guest_site);
+    guest_site_instance =
+        content::SiteInstance::CreateForGuest(browser_context(), guest_site);
   }
-  WebContents::CreateParams params(
-      owner_render_process_host->GetBrowserContext(),
-      std::move(guest_site_instance));
+  WebContents::CreateParams params(browser_context(),
+                                   std::move(guest_site_instance));
   params.guest_delegate = this;
   // TODO(erikchen): Fix ownership semantics for guest views.
   // https://crbug.com/832879.
@@ -1067,8 +1069,9 @@
 void WebViewGuest::PushWebViewStateToIOThread() {
   const GURL& site_url = web_contents()->GetSiteInstance()->GetSiteURL();
   content::StoragePartitionConfig storage_partition_config =
-      content::StoragePartitionConfig::CreateDefault();
-  if (!GetGuestPartitionConfigForSite(site_url, &storage_partition_config)) {
+      content::StoragePartitionConfig::CreateDefault(browser_context());
+  if (!GetGuestPartitionConfigForSite(browser_context(), site_url,
+                                      &storage_partition_config)) {
     NOTREACHED();
     return;
   }
diff --git a/extensions/browser/guest_view/web_view/web_view_guest.h b/extensions/browser/guest_view/web_view/web_view_guest.h
index 20782fc..f842b97d 100644
--- a/extensions/browser/guest_view/web_view/web_view_guest.h
+++ b/extensions/browser/guest_view/web_view/web_view_guest.h
@@ -56,6 +56,7 @@
   // the partition requested by it. The format for that URL is:
   // chrome-guest://partition_domain/persist?partition_name
   static bool GetGuestPartitionConfigForSite(
+      content::BrowserContext* browser_context,
       const GURL& site,
       content::StoragePartitionConfig* storage_partition_config);
 
diff --git a/extensions/common/api/automation.idl b/extensions/common/api/automation.idl
index c9c91d30..36ba361 100644
--- a/extensions/common/api/automation.idl
+++ b/extensions/common/api/automation.idl
@@ -244,7 +244,6 @@
     iframePresentational,
     ignored,
     image,
-    imageMap,
     imeCandidate,
     inlineTextBox,
     inputTime,
diff --git a/extensions/strings/BUILD.gn b/extensions/strings/BUILD.gn
index 8c707a1..a4d6546 100644
--- a/extensions/strings/BUILD.gn
+++ b/extensions/strings/BUILD.gn
@@ -12,6 +12,6 @@
   source = "extensions_strings.grd"
   outputs =
       [ "grit/extensions_strings.h" ] +
-      process_file_template(locales_with_fake_bidi,
+      process_file_template(locales_with_pseudolocales,
                             [ "extensions_strings_{{source_name_part}}.pak" ])
 }
diff --git a/fuchsia/engine/BUILD.gn b/fuchsia/engine/BUILD.gn
index bca90794..52c6634 100644
--- a/fuchsia/engine/BUILD.gn
+++ b/fuchsia/engine/BUILD.gn
@@ -62,7 +62,7 @@
   visibility = [ ":*" ]
 }
 
-foreach(locale, locales_with_fake_bidi) {
+foreach(locale, locales_with_pseudolocales) {
   repack("web_engine_locale_${locale}_pak") {
     # WebEngine requires the following locale-specific resources:
     # 1. Locale settings (e.g. default encoding, accept-languages per locale).
@@ -153,7 +153,7 @@
   # package.
   deps += [ ":web_engine_pak" ]
   data = [ "$root_gen_dir/common_resources.pak" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     deps += [ ":web_engine_locale_${locale}_pak" ]
     data += [ "$root_gen_dir/locales/${locale}.pak" ]
   }
diff --git a/fuchsia/engine/context_provider_impl.cc b/fuchsia/engine/context_provider_impl.cc
index 85e7f980..a62bb75 100644
--- a/fuchsia/engine/context_provider_impl.cc
+++ b/fuchsia/engine/context_provider_impl.cc
@@ -170,6 +170,7 @@
       switches::kGoogleApiKey,
       switches::kMaxDecodedImageSizeMb,
       switches::kRendererProcessLimit,
+      switches::kUseCmdDecoder,
       switches::kVulkanHeapMemoryLimitMb,
       switches::kVulkanSyncCpuMemoryLimitMb,
       switches::kWebglAntialiasingMode,
diff --git a/fuchsia/engine/renderer/web_engine_content_renderer_client.cc b/fuchsia/engine/renderer/web_engine_content_renderer_client.cc
index 3663470..80bdf1d 100644
--- a/fuchsia/engine/renderer/web_engine_content_renderer_client.cc
+++ b/fuchsia/engine/renderer/web_engine_content_renderer_client.cc
@@ -128,7 +128,12 @@
 WebEngineContentRendererClient::GetWebEngineRenderFrameObserverForRenderFrameId(
     int render_frame_id) const {
   auto iter = render_frame_id_to_observer_map_.find(render_frame_id);
-  DCHECK(iter != render_frame_id_to_observer_map_.end());
+
+  // TODO(https://crbug.com/1181062): Change this back to a DCHECK once the root
+  // cause of this bug has been found.
+  CHECK(iter != render_frame_id_to_observer_map_.end())
+      << "No WebEngineRenderFrameObserver for RenderFrame ID "
+      << render_frame_id;
   return iter->second.get();
 }
 
diff --git a/fuchsia/engine/renderer/web_engine_render_frame_observer.cc b/fuchsia/engine/renderer/web_engine_render_frame_observer.cc
index 81202275..ee76ae6 100644
--- a/fuchsia/engine/renderer/web_engine_render_frame_observer.cc
+++ b/fuchsia/engine/renderer/web_engine_render_frame_observer.cc
@@ -4,6 +4,7 @@
 
 #include "fuchsia/engine/renderer/web_engine_render_frame_observer.h"
 
+#include "base/logging.h"
 #include "content/public/renderer/render_frame.h"
 
 WebEngineRenderFrameObserver::WebEngineRenderFrameObserver(
@@ -16,9 +17,19 @@
           std::move(on_render_frame_deleted_callback)) {
   DCHECK(render_frame);
   DCHECK(on_render_frame_deleted_callback_);
+
+  // TODO(https://crbug.com/1181062): Remove this once the root cause of this
+  // bug has been found.
+  LOG(INFO) << "WebEngineRenderFrameObserver created for RenderFrame ID "
+            << routing_id();
 }
 
-WebEngineRenderFrameObserver::~WebEngineRenderFrameObserver() = default;
+WebEngineRenderFrameObserver::~WebEngineRenderFrameObserver() {
+  // TODO(https://crbug.com/1181062): Remove this once the root cause of this
+  // bug has been found.
+  LOG(INFO) << "WebEngineRenderFrameObserver deleted for RenderFrame ID "
+            << routing_id();
+}
 
 void WebEngineRenderFrameObserver::OnDestruct() {
   std::move(on_render_frame_deleted_callback_).Run(routing_id());
diff --git a/fuchsia/engine/renderer/web_engine_url_loader_throttle_provider.cc b/fuchsia/engine/renderer/web_engine_url_loader_throttle_provider.cc
index f489318..eaf0ae2 100644
--- a/fuchsia/engine/renderer/web_engine_url_loader_throttle_provider.cc
+++ b/fuchsia/engine/renderer/web_engine_url_loader_throttle_provider.cc
@@ -31,6 +31,10 @@
     const blink::WebURLRequest& request) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  // TODO(https://crbug.com/1181062): Remove this once the root cause of this
+  // bug has been found.
+  CHECK_NE(render_frame_id, MSG_ROUTING_NONE);
+
   blink::WebVector<std::unique_ptr<blink::URLLoaderThrottle>> throttles;
   throttles.emplace_back(std::make_unique<WebEngineURLLoaderThrottle>(
       content_renderer_client_
diff --git a/google_apis/drive/base_requests_server_unittest.cc b/google_apis/drive/base_requests_server_unittest.cc
index 5ff0072..e709c1a 100644
--- a/google_apis/drive/base_requests_server_unittest.cc
+++ b/google_apis/drive/base_requests_server_unittest.cc
@@ -25,7 +25,6 @@
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "services/network/test/fake_test_cert_verifier_params_factory.h"
-#include "services/network/test/test_network_service_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace google_apis {
@@ -52,14 +51,13 @@
         network_context_.BindNewPipeAndPassReceiver(),
         std::move(context_params));
 
-    mojo::PendingRemote<network::mojom::NetworkServiceClient>
-        network_service_client_remote;
-    network_service_client_ =
-        std::make_unique<network::TestNetworkServiceClient>(
-            network_service_client_remote.InitWithNewPipeAndPassReceiver());
-    network_service_remote->SetClient(
-        std::move(network_service_client_remote),
-        network::mojom::NetworkServiceParams::New());
+    mojo::PendingReceiver<network::mojom::URLLoaderNetworkServiceObserver>
+        default_observer_receiver;
+    network::mojom::NetworkServiceParamsPtr network_service_params =
+        network::mojom::NetworkServiceParams::New();
+    network_service_params->default_observer =
+        default_observer_receiver.InitWithNewPipeAndPassRemote();
+    network_service_remote->SetParams(std::move(network_service_params));
 
     network::mojom::URLLoaderFactoryParamsPtr params =
         network::mojom::URLLoaderFactoryParams::New();
@@ -97,7 +95,6 @@
   net::EmbeddedTestServer test_server_;
   std::unique_ptr<RequestSender> request_sender_;
   std::unique_ptr<network::mojom::NetworkService> network_service_;
-  std::unique_ptr<network::mojom::NetworkServiceClient> network_service_client_;
   mojo::Remote<network::mojom::NetworkContext> network_context_;
   mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_;
   scoped_refptr<network::WeakWrapperSharedURLLoaderFactory>
diff --git a/google_apis/drive/base_requests_unittest.cc b/google_apis/drive/base_requests_unittest.cc
index 0e2fd617..cfeb88d 100644
--- a/google_apis/drive/base_requests_unittest.cc
+++ b/google_apis/drive/base_requests_unittest.cc
@@ -27,7 +27,6 @@
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "services/network/test/fake_test_cert_verifier_params_factory.h"
-#include "services/network/test/test_network_service_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace google_apis {
@@ -127,14 +126,13 @@
         network_context_.BindNewPipeAndPassReceiver(),
         std::move(context_params));
 
-    mojo::PendingRemote<network::mojom::NetworkServiceClient>
-        network_service_client_remote;
-    network_service_client_ =
-        std::make_unique<network::TestNetworkServiceClient>(
-            network_service_client_remote.InitWithNewPipeAndPassReceiver());
-    network_service_remote->SetClient(
-        std::move(network_service_client_remote),
-        network::mojom::NetworkServiceParams::New());
+    mojo::PendingReceiver<network::mojom::URLLoaderNetworkServiceObserver>
+        default_observer_receiver;
+    network::mojom::NetworkServiceParamsPtr network_service_params =
+        network::mojom::NetworkServiceParams::New();
+    network_service_params->default_observer =
+        default_observer_receiver.InitWithNewPipeAndPassRemote();
+    network_service_remote->SetParams(std::move(network_service_params));
 
     network::mojom::URLLoaderFactoryParamsPtr params =
         network::mojom::URLLoaderFactoryParams::New();
@@ -179,7 +177,6 @@
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::MainThreadType::IO};
   std::unique_ptr<network::mojom::NetworkService> network_service_;
-  std::unique_ptr<network::mojom::NetworkServiceClient> network_service_client_;
   mojo::Remote<network::mojom::NetworkContext> network_context_;
   mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_;
   scoped_refptr<network::WeakWrapperSharedURLLoaderFactory>
diff --git a/google_apis/drive/drive_api_requests_unittest.cc b/google_apis/drive/drive_api_requests_unittest.cc
index 75295681..203ce98 100644
--- a/google_apis/drive/drive_api_requests_unittest.cc
+++ b/google_apis/drive/drive_api_requests_unittest.cc
@@ -36,7 +36,6 @@
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/test/fake_test_cert_verifier_params_factory.h"
 #include "services/network/test/test_network_context_client.h"
-#include "services/network/test/test_network_service_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace google_apis {
@@ -141,14 +140,13 @@
         network_context_.BindNewPipeAndPassReceiver(),
         std::move(context_params));
 
-    mojo::PendingRemote<network::mojom::NetworkServiceClient>
-        network_service_client_remote;
-    network_service_client_ =
-        std::make_unique<network::TestNetworkServiceClient>(
-            network_service_client_remote.InitWithNewPipeAndPassReceiver());
-    network_service_remote->SetClient(
-        std::move(network_service_client_remote),
-        network::mojom::NetworkServiceParams::New());
+    mojo::PendingReceiver<network::mojom::URLLoaderNetworkServiceObserver>
+        default_observer_receiver;
+    network::mojom::NetworkServiceParamsPtr network_service_params =
+        network::mojom::NetworkServiceParams::New();
+    network_service_params->default_observer =
+        default_observer_receiver.InitWithNewPipeAndPassRemote();
+    network_service_remote->SetParams(std::move(network_service_params));
 
     mojo::PendingRemote<network::mojom::NetworkContextClient>
         network_context_client_remote;
@@ -231,7 +229,6 @@
   std::unique_ptr<RequestSender> request_sender_;
   std::unique_ptr<DriveApiUrlGenerator> url_generator_;
   std::unique_ptr<network::mojom::NetworkService> network_service_;
-  std::unique_ptr<network::mojom::NetworkServiceClient> network_service_client_;
   std::unique_ptr<network::mojom::NetworkContextClient> network_context_client_;
   mojo::Remote<network::mojom::NetworkContext> network_context_;
   mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_;
diff --git a/google_apis/drive/files_list_request_runner_unittest.cc b/google_apis/drive/files_list_request_runner_unittest.cc
index b09f7914..4aded94 100644
--- a/google_apis/drive/files_list_request_runner_unittest.cc
+++ b/google_apis/drive/files_list_request_runner_unittest.cc
@@ -25,7 +25,6 @@
 #include "services/network/network_service.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/test/fake_test_cert_verifier_params_factory.h"
-#include "services/network/test/test_network_service_client.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -84,14 +83,13 @@
         network_context_.BindNewPipeAndPassReceiver(),
         std::move(context_params));
 
-    mojo::PendingRemote<network::mojom::NetworkServiceClient>
-        network_service_client_remote;
-    network_service_client_ =
-        std::make_unique<network::TestNetworkServiceClient>(
-            network_service_client_remote.InitWithNewPipeAndPassReceiver());
-    network_service_remote->SetClient(
-        std::move(network_service_client_remote),
-        network::mojom::NetworkServiceParams::New());
+    mojo::PendingReceiver<network::mojom::URLLoaderNetworkServiceObserver>
+        default_observer_receiver;
+    network::mojom::NetworkServiceParamsPtr network_service_params =
+        network::mojom::NetworkServiceParams::New();
+    network_service_params->default_observer =
+        default_observer_receiver.InitWithNewPipeAndPassRemote();
+    network_service_remote->SetParams(std::move(network_service_params));
 
     network::mojom::URLLoaderFactoryParamsPtr params =
         network::mojom::URLLoaderFactoryParams::New();
@@ -161,7 +159,6 @@
   net::EmbeddedTestServer test_server_;
   std::unique_ptr<FilesListRequestRunner> runner_;
   std::unique_ptr<network::mojom::NetworkService> network_service_;
-  std::unique_ptr<network::mojom::NetworkServiceClient> network_service_client_;
   mojo::Remote<network::mojom::NetworkContext> network_context_;
   mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_;
   scoped_refptr<network::WeakWrapperSharedURLLoaderFactory>
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index ee3cf8c..3499f160 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -15362,11 +15362,12 @@
       name: "Win x64 Builder (reclient)"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builder:Win x64 Builder (reclient)"
+      dimensions: "builderless:1"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
       dimensions: "os:Windows-10"
       dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
diff --git a/infra/config/subprojects/chromium/ci.star b/infra/config/subprojects/chromium/ci.star
index 34b8792..0f1eb71 100644
--- a/infra/config/subprojects/chromium/ci.star
+++ b/infra/config/subprojects/chromium/ci.star
@@ -3597,6 +3597,7 @@
 
 ci.fyi_windows_builder(
     name = "Win x64 Builder (reclient)",
+    builderless = True,
     console_view_entry = consoles.console_view_entry(
         category = "win",
         short_name = "re",
diff --git a/ios/chrome/app/BUILD.gn b/ios/chrome/app/BUILD.gn
index abce7f5..b0500b1 100644
--- a/ios/chrome/app/BUILD.gn
+++ b/ios/chrome/app/BUILD.gn
@@ -2,11 +2,11 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/apple/compile_entitlements.gni")
 import("//build/apple/tweak_info_plist.gni")
 import("//build/buildflag_header.gni")
 import("//build/config/ios/ios_sdk.gni")
 import("//build/config/ios/rules.gni")
+import("//build/config/mac/base_rules.gni")
 import("//ios/build/chrome_build.gni")
 import("//ios/build/config.gni")
 import("//ios/chrome/features.gni")
diff --git a/ios/chrome/app/application_delegate/user_activity_handler.mm b/ios/chrome/app/application_delegate/user_activity_handler.mm
index b15ae0b..6f8e382 100644
--- a/ios/chrome/app/application_delegate/user_activity_handler.mm
+++ b/ios/chrome/app/application_delegate/user_activity_handler.mm
@@ -62,6 +62,10 @@
 NSString* const kSiriShortcutSearchInChrome = @"SearchInChromeIntent";
 NSString* const kSiriShortcutOpenInIncognito = @"OpenInChromeIncognitoIntent";
 
+// Constants for compatible mode for user activities.
+NSString* const kRegularMode = @"RegularMode";
+NSString* const kIncognitoMode = @"IncognitoMode";
+
 std::vector<GURL> createGURLVectorFromIntentURLs(NSArray<NSURL*>* intentURLs) {
   std::vector<GURL> URLs;
   for (NSURL* URL in intentURLs) {
@@ -70,6 +74,25 @@
   return URLs;
 }
 
+// Returns the compatible mode array for an user activity.
+NSArray* CompatibleModeForActivityType(NSString* activityType) {
+  if (activityType == CSSearchableItemActionType ||
+      activityType == kShortcutNewSearch ||
+      activityType == kShortcutVoiceSearch ||
+      activityType == kShortcutQRScanner ||
+      activityType == kSiriShortcutSearchInChrome) {
+    return @[ kRegularMode, kIncognitoMode ];
+  } else if (activityType == kSiriShortcutOpenInChrome) {
+    return @[ kRegularMode ];
+  } else if (activityType == kShortcutNewIncognitoSearch ||
+             activityType == kSiriShortcutOpenInIncognito) {
+    return @[ kIncognitoMode ];
+  } else {
+    NOTREACHED();
+  }
+  return nil;
+}
+
 }  // namespace
 
 @interface UserActivityHandler ()
@@ -537,12 +560,16 @@
 
 + (BOOL)canProceeedWithUserActivity:(NSUserActivity*)userActivity
                         prefService:(PrefService*)prefService {
-  return ([userActivity.activityType
-              isEqualToString:kSiriShortcutOpenInChrome] &&
-          IsIncognitoModeForced(prefService)) ||
-         ([userActivity.activityType
-              isEqualToString:kSiriShortcutOpenInIncognito] &&
-          IsIncognitoModeDisabled(prefService));
+  NSArray* array = CompatibleModeForActivityType(userActivity.activityType);
+
+  if (IsIncognitoModeDisabled(prefService)) {
+    return [array containsObject:kRegularMode];
+  } else if (IsIncognitoModeForced(prefService)) {
+    return [array containsObject:kIncognitoMode];
+  }
+
+  // Return YES if the compatible mode array is not nil.
+  return array != nil;
 }
 
 #pragma mark - Internal methods.
diff --git a/ios/chrome/app/strings/BUILD.gn b/ios/chrome/app/strings/BUILD.gn
index e6be093..cd583eb 100644
--- a/ios/chrome/app/strings/BUILD.gn
+++ b/ios/chrome/app/strings/BUILD.gn
@@ -17,7 +17,7 @@
   source = "ios_strings.grd"
   output_dir = "$root_gen_dir/ios/chrome"
   outputs = [ "grit/ios_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "ios_strings_$locale.pak" ]
   }
 }
@@ -26,7 +26,7 @@
   source = "ios_chromium_strings.grd"
   output_dir = "$root_gen_dir/ios/chrome"
   outputs = [ "grit/ios_chromium_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "ios_chromium_strings_$locale.pak" ]
   }
 }
@@ -35,7 +35,7 @@
   source = "ios_google_chrome_strings.grd"
   output_dir = "$root_gen_dir/ios/chrome"
   outputs = [ "grit/ios_google_chrome_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "ios_google_chrome_strings_$locale.pak" ]
   }
 }
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index f241c7e..1a7c1888 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -877,7 +877,7 @@
         Make Searches and Browsing Better
       </message>
       <message name="IDS_IOS_GOOGLE_SERVICES_SETTINGS_ENTER_PASSPHRASE_TO_START_SYNC" desc="Title button to ask the user to tap their passphrase. The sync is blocked until the correct passphrase is entered">
-        Enter passphrase to start sync.
+        To start sync, enter your passphrase
       </message>
       <message name="IDS_IOS_GOOGLE_SERVICES_SETTINGS_IMPROVE_CHROME_DETAIL" desc="Feature detail text in the settings for the user enable or disable. Related with 'Help improve Chrome's features and performance' feature. [iOS only]">
         Automatically sends usage statistics and crash reports to Google.
@@ -901,7 +901,7 @@
         Sync Is Disabled by Your Administrator
       </message>
       <message name="IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_ENCRYPTION_FIX_NOW" desc="Title button to ask the user to reauthenticate. Syncing of encrypted types (e.g. passwords) is blocked until the user completes the reauth flow.">
-        Fix now.
+        Verify it's you
       </message>
       <message name="IDS_IOS_HELP_ACCESSIBILITY_LABEL" desc="Used as the accessibility label read by screen readers for a Help button. [iOS only]">
         Help
@@ -1129,6 +1129,9 @@
       <message name="IDS_IOS_MANUAL_FALLBACK_USE_OTHER_PASSWORD" desc="The title for the manual fallback UI with all passwords without filter." meaning="The title for a list where the user is about to select a password or username to be filled in a form [Length: 10em]">
         Passwords
       </message>
+      <message name="IDS_IOS_MANUAL_FALLBACK_SUGGEST_PASSWORD_WITH_DOTS" desc="The title for the button in the manual fallback passwords UI that is used to trigger password generation on the active field. [30em]">
+        Suggest password...
+      </message>
       <message name="IDS_IOS_MANUAL_FALLBACK_USE_OTHER_PASSWORD_WITH_DOTS" desc="The title for the button in the manual fallback passwords UI that is used to open a list of all the user credentials where they can select one to fill a form. This button is showed at the same time as a different button with  IDS_IOS_MANUAL_FALLBACK_MANAGE_PASSWORDS as title. [30em]">
         Use Other Password...
       </message>
@@ -2235,7 +2238,7 @@
         Passphrase encryption doesn’t include payment methods and addresses from Google Pay. Only someone with your passphrase can read your encrypted data. The passphrase is not sent to or stored by Google. If you forget your passphrase or want to change this setting, you will need to reset sync. <ph name="BEGIN_LINK">BEGIN_LINK</ph>Learn more<ph name="END_LINK">END_LINK</ph>
       </message>
       <message name="IDS_IOS_SYNC_ENCRYPTION_FIX_NOW" desc="Title displayed when the signed in user needs to reauthenticate. Syncing of encrypted types (e.g. passwords) is blocked until the user completes the reauth flow.">
-        Fix Now
+        Verify It's You
       </message>
       <message name="IDS_IOS_SYNC_ENCRYPTION_TITLE" desc="The title for the Sync Encryption item [iOS only]">
         Encryption
@@ -2263,7 +2266,7 @@
         Sync isn't working.
       </message>
       <message name="IDS_IOS_SYNC_PASSWORDS_ERROR_DESCRIPTION" desc="The error message to display when a generic error prevents encrypted sync datatypes (passwords) from working.">
-        Error syncing passwords.
+        Password sync isn't working
       </message>
       <message name="IDS_IOS_SYNC_ERROR_INFO_OUT_OF_DATE" desc="The error message to display when the accounts info are out of date. [Length: 100em, can be multiple lines] [iOS only]">
         Account sign-in details are out of date. Update to start sync.
@@ -2296,7 +2299,7 @@
         If you forget your passphrase or want to change this setting, <ph name="BEGIN_LINK">BEGIN_LINK</ph>reset sync<ph name="END_LINK">END_LINK</ph>
       </message>
       <message name="IDS_IOS_SYNC_PASSWORDS_ERROR_TITLE" desc="Title of the error message shown for sync errors when it affects passwords only. [iOS only]">
-        Error Syncing Passwords
+        Password Sync Isn't Working
       </message>
       <message name="IDS_IOS_SYNC_SETTINGS_NOT_CONFIRMED" desc="The error message to display when sign-in was interrupted and the user didn't review the sync settings.">
         Initial sync setup was not finished. Sync is off.
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_GOOGLE_SERVICES_SETTINGS_ENTER_PASSPHRASE_TO_START_SYNC.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_GOOGLE_SERVICES_SETTINGS_ENTER_PASSPHRASE_TO_START_SYNC.png.sha1
index 694a2a3..b0db6ee3 100644
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_GOOGLE_SERVICES_SETTINGS_ENTER_PASSPHRASE_TO_START_SYNC.png.sha1
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_GOOGLE_SERVICES_SETTINGS_ENTER_PASSPHRASE_TO_START_SYNC.png.sha1
@@ -1 +1 @@
-2a94fd9890090b52bd53dfbe0ab171d68541086a
\ No newline at end of file
+9272bebc93956b5db16964e2d9ccc89c3def8fc6
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_ENCRYPTION_FIX_NOW.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_ENCRYPTION_FIX_NOW.png.sha1
index abcd7875..a98181a4 100644
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_ENCRYPTION_FIX_NOW.png.sha1
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_ENCRYPTION_FIX_NOW.png.sha1
@@ -1 +1 @@
-244e185d254eae24688ae387476e3d716947aed4
\ No newline at end of file
+9fa2666cb7ff4870838eeb4c8744f679f063518a
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_MANUAL_FALLBACK_SUGGEST_PASSWORD_WITH_DOTS.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_MANUAL_FALLBACK_SUGGEST_PASSWORD_WITH_DOTS.png.sha1
new file mode 100644
index 0000000..081c8803
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_MANUAL_FALLBACK_SUGGEST_PASSWORD_WITH_DOTS.png.sha1
@@ -0,0 +1 @@
+270380673c847e5a7827a35d5ca41510ca890274
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SYNC_ENCRYPTION_FIX_NOW.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SYNC_ENCRYPTION_FIX_NOW.png.sha1
index 64e77ff..a98181a4 100644
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SYNC_ENCRYPTION_FIX_NOW.png.sha1
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SYNC_ENCRYPTION_FIX_NOW.png.sha1
@@ -1 +1 @@
-48ae12ad30af6d2654cffd4b0d68db33e00ffefa
\ No newline at end of file
+9fa2666cb7ff4870838eeb4c8744f679f063518a
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SYNC_PASSWORDS_ERROR_DESCRIPTION.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SYNC_PASSWORDS_ERROR_DESCRIPTION.png.sha1
index 64e77ff..3388d77b 100644
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SYNC_PASSWORDS_ERROR_DESCRIPTION.png.sha1
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SYNC_PASSWORDS_ERROR_DESCRIPTION.png.sha1
@@ -1 +1 @@
-48ae12ad30af6d2654cffd4b0d68db33e00ffefa
\ No newline at end of file
+8b1b72bde9bc0490edeb06206a26a6b7e3d498df
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SYNC_PASSWORDS_ERROR_TITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SYNC_PASSWORDS_ERROR_TITLE.png.sha1
index 2ed17209..3388d77b 100644
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SYNC_PASSWORDS_ERROR_TITLE.png.sha1
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SYNC_PASSWORDS_ERROR_TITLE.png.sha1
@@ -1 +1 @@
-c85314e60c88df143e720cb14ab9a6f0a2bf41c5
\ No newline at end of file
+8b1b72bde9bc0490edeb06206a26a6b7e3d498df
\ No newline at end of file
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_address_profile/save_address_profile_infobar_banner_interaction_handler.mm b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_address_profile/save_address_profile_infobar_banner_interaction_handler.mm
index 0fdddd8..4d95748 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_address_profile/save_address_profile_infobar_banner_interaction_handler.mm
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_address_profile/save_address_profile_infobar_banner_interaction_handler.mm
@@ -37,7 +37,7 @@
 void SaveAddressProfileInfobarBannerInteractionHandler::BannerVisibilityChanged(
     InfoBarIOS* infobar,
     bool visible) {
-  if (!visible)
+  if (!visible && !infobar->accepted())
     GetInfobarDelegate(infobar)->InfoBarDismissed();
 }
 
diff --git a/ios/chrome/browser/passwords/password_controller.h b/ios/chrome/browser/passwords/password_controller.h
index 110fbcf..f954edb 100644
--- a/ios/chrome/browser/passwords/password_controller.h
+++ b/ios/chrome/browser/passwords/password_controller.h
@@ -11,6 +11,7 @@
 
 #import "components/autofill/ios/browser/form_suggestion_provider.h"
 #import "components/password_manager/ios/password_form_helper.h"
+#import "components/password_manager/ios/password_generation_provider.h"
 #import "components/password_manager/ios/password_manager_client_bridge.h"
 #import "components/password_manager/ios/password_manager_driver_bridge.h"
 #import "ios/chrome/browser/passwords/ios_chrome_password_manager_client.h"
@@ -49,6 +50,10 @@
 // An object that can provide suggestions from this PasswordController.
 @property(nonatomic, readonly) id<FormSuggestionProvider> suggestionProvider;
 
+// An object that can provide password generation from this PasswordController.
+@property(nonatomic, readonly) id<PasswordGenerationProvider>
+    generationProvider;
+
 // The PasswordManagerClient owned by this PasswordController.
 @property(nonatomic, readonly)
     password_manager::PasswordManagerClient* passwordManagerClient;
diff --git a/ios/chrome/browser/passwords/password_controller.mm b/ios/chrome/browser/passwords/password_controller.mm
index fe0ca7d..f66fce0 100644
--- a/ios/chrome/browser/passwords/password_controller.mm
+++ b/ios/chrome/browser/passwords/password_controller.mm
@@ -256,6 +256,12 @@
   return _sharedPasswordController;
 }
 
+#pragma mark - PasswordGenerationProvider
+
+- (id<PasswordGenerationProvider>)generationProvider {
+  return _sharedPasswordController;
+}
+
 #pragma mark - IOSChromePasswordManagerClientBridge
 
 - (WebState*)webState {
diff --git a/ios/chrome/browser/passwords/password_tab_helper.h b/ios/chrome/browser/passwords/password_tab_helper.h
index 5df5d34..8fd7d74 100644
--- a/ios/chrome/browser/passwords/password_tab_helper.h
+++ b/ios/chrome/browser/passwords/password_tab_helper.h
@@ -13,6 +13,7 @@
 @protocol FormSuggestionProvider;
 @class PasswordController;
 @protocol PasswordControllerDelegate;
+@protocol PasswordGenerationProvider;
 @protocol PasswordsUiDelegate;
 @class UIViewController;
 
@@ -49,6 +50,10 @@
   // Returns the PasswordManager owned by the PasswordController.
   password_manager::PasswordManager* GetPasswordManager();
 
+  // Returns an object that can provide password generation from the
+  // PasswordController. May return nil.
+  id<PasswordGenerationProvider> GetPasswordGenerationProvider();
+
  private:
   friend class web::WebStateUserData<PasswordTabHelper>;
 
diff --git a/ios/chrome/browser/passwords/password_tab_helper.mm b/ios/chrome/browser/passwords/password_tab_helper.mm
index a923fb3..78a7c6ec 100644
--- a/ios/chrome/browser/passwords/password_tab_helper.mm
+++ b/ios/chrome/browser/passwords/password_tab_helper.mm
@@ -51,6 +51,11 @@
   return controller_.passwordManager;
 }
 
+id<PasswordGenerationProvider>
+PasswordTabHelper::GetPasswordGenerationProvider() {
+  return controller_.generationProvider;
+}
+
 PasswordTabHelper::PasswordTabHelper(web::WebState* web_state)
     : controller_([[PasswordController alloc] initWithWebState:web_state]) {
   web_state->AddObserver(this);
diff --git a/ios/chrome/browser/sync/profile_sync_service_factory_unittest.cc b/ios/chrome/browser/sync/profile_sync_service_factory_unittest.cc
index 3fea1596..9a042e6 100644
--- a/ios/chrome/browser/sync/profile_sync_service_factory_unittest.cc
+++ b/ios/chrome/browser/sync/profile_sync_service_factory_unittest.cc
@@ -60,6 +60,7 @@
     datatypes.push_back(syncer::AUTOFILL_PROFILE);
     datatypes.push_back(syncer::AUTOFILL_WALLET_DATA);
     datatypes.push_back(syncer::AUTOFILL_WALLET_METADATA);
+    datatypes.push_back(syncer::AUTOFILL_WALLET_OFFER);
     datatypes.push_back(syncer::BOOKMARKS);
     datatypes.push_back(syncer::DEVICE_INFO);
     datatypes.push_back(syncer::HISTORY_DELETE_DIRECTIVES);
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.mm b/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.mm
index 751f9d9..9b48638 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.mm
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.mm
@@ -158,7 +158,8 @@
   [self.childCoordinators removeAllObjects];
 }
 
-- (void)startPasswordsFromButton:(UIButton*)button {
+- (void)startPasswordsFromButton:(UIButton*)button
+          invokedOnPasswordField:(BOOL)invokedOnPasswordField {
   WebStateList* webStateList = self.browser->GetWebStateList();
   DCHECK(webStateList->GetActiveWebState());
   const GURL& URL = webStateList->GetActiveWebState()->GetLastCommittedURL();
@@ -167,7 +168,8 @@
           initWithBaseViewController:self.baseViewController
                              browser:self.browser
                                  URL:URL
-                    injectionHandler:self.injectionHandler];
+                    injectionHandler:self.injectionHandler
+              invokedOnPasswordField:invokedOnPasswordField];
   passwordCoordinator.delegate = self;
   if (IsIPadIdiom()) {
     [passwordCoordinator presentFromButton:button];
@@ -249,7 +251,10 @@
 
 - (void)passwordButtonPressed:(UIButton*)sender {
   [self stopChildren];
-  [self startPasswordsFromButton:sender];
+  BOOL invokedOnPasswordField =
+      [self.formInputAccessoryMediator lastFocusedFieldWasPassword];
+  [self startPasswordsFromButton:sender
+          invokedOnPasswordField:invokedOnPasswordField];
   [self.formInputAccessoryViewController lockManualFallbackView];
   [self.formInputAccessoryMediator disableSuggestions];
 }
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.h b/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.h
index 40d289a0..d15786b 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.h
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.h
@@ -77,6 +77,9 @@
 // Stops observing all objects.
 - (void)disconnect;
 
+// Returns YES if the last focused field is of type 'password'.
+- (BOOL)lastFocusedFieldWasPassword;
+
 @end
 
 // Methods to allow injection in tests.
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.mm b/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.mm
index b69c619..085c7187 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.mm
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.mm
@@ -272,6 +272,10 @@
   }
 }
 
+- (BOOL)lastFocusedFieldWasPassword {
+  return _lastSeenParams.field_type == autofill::kPasswordFieldType;
+}
+
 #pragma mark - KeyboardObserverHelperConsumer
 
 - (void)keyboardDidStayOnScreen {
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/BUILD.gn b/ios/chrome/browser/ui/autofill/manual_fill/BUILD.gn
index 80933d3..e4eb9634 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/BUILD.gn
+++ b/ios/chrome/browser/ui/autofill/manual_fill/BUILD.gn
@@ -42,6 +42,8 @@
     "//components/autofill/ios/form_util",
     "//components/keyed_service/core:core",
     "//components/password_manager/core/browser",
+    "//components/password_manager/core/common",
+    "//components/password_manager/ios",
     "//ios/chrome/app/strings:ios_strings_grit",
     "//ios/chrome/browser",
     "//ios/chrome/browser/autofill",
@@ -51,6 +53,7 @@
     "//ios/chrome/browser/favicon",
     "//ios/chrome/browser/main",
     "//ios/chrome/browser/passwords",
+    "//ios/chrome/browser/sync",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/autofill/manual_fill:manual_fill_ui",
     "//ios/chrome/browser/ui/commands",
@@ -228,8 +231,10 @@
     "//base/test:test_support",
     "//components/autofill/core/browser:test_support",
     "//components/password_manager/core/browser",
+    "//components/password_manager/core/common",
     "//components/strings",
     "//ios/chrome/app/strings:ios_strings_grit",
+    "//ios/chrome/browser/ui/authentication:eg_test_support+eg2",
     "//ios/chrome/browser/ui/autofill:eg_test_support+eg2",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/ui/util:eg_test_support+eg2",
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_all_password_coordinator.mm b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_all_password_coordinator.mm
index df5486b..9711881 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_all_password_coordinator.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_all_password_coordinator.mm
@@ -10,6 +10,7 @@
 #include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
 #import "ios/chrome/browser/main/browser.h"
 #include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h"
+#include "ios/chrome/browser/sync/sync_setup_service_factory.h"
 #import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_injection_handler.h"
 #import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_mediator.h"
 #import "ios/chrome/browser/ui/autofill/manual_fill/password_list_navigator.h"
@@ -17,6 +18,7 @@
 #import "ios/chrome/browser/ui/table_view/table_view_animator.h"
 #import "ios/chrome/browser/ui/table_view/table_view_navigation_controller.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
+#import "ios/chrome/browser/web_state_list/web_state_list.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -51,9 +53,16 @@
   FaviconLoader* faviconLoader =
       IOSChromeFaviconLoaderFactory::GetForBrowserState(
           self.browser->GetBrowserState());
+  web::WebState* webState =
+      self.browser->GetWebStateList()->GetActiveWebState();
+  SyncSetupService* syncService = SyncSetupServiceFactory::GetForBrowserState(
+      self.browser->GetBrowserState());
   self.passwordMediator =
       [[ManualFillPasswordMediator alloc] initWithPasswordStore:passwordStore
-                                                  faviconLoader:faviconLoader];
+                                                  faviconLoader:faviconLoader
+                                                       webState:webState
+                                                    syncService:syncService
+                                         invokedOnPasswordField:NO];
   [self.passwordMediator fetchPasswordsForURL:GURL::EmptyGURL()];
   self.passwordMediator.actionSectionEnabled = NO;
   self.passwordMediator.consumer = self.passwordViewController;
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_coordinator.h b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_coordinator.h
index ad0550cf..3c25c04 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_coordinator.h
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_coordinator.h
@@ -36,6 +36,7 @@
                                        URL:(const GURL&)URL
                           injectionHandler:
                               (ManualFillInjectionHandler*)injectionHandler
+                    invokedOnPasswordField:(BOOL)invokedOnPasswordField
     NS_DESIGNATED_INITIALIZER;
 
 // Unavailable, use
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_coordinator.mm b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_coordinator.mm
index 326c85d5..6fc5565b 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_coordinator.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_coordinator.mm
@@ -11,11 +11,13 @@
 #include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
 #import "ios/chrome/browser/main/browser.h"
 #include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h"
+#include "ios/chrome/browser/sync/sync_setup_service_factory.h"
 #import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_injection_handler.h"
 #import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_mediator.h"
 #import "ios/chrome/browser/ui/autofill/manual_fill/password_list_navigator.h"
 #import "ios/chrome/browser/ui/autofill/manual_fill/password_view_controller.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
+#import "ios/chrome/browser/web_state_list/web_state_list.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -47,7 +49,8 @@
                                    browser:(Browser*)browser
                                        URL:(const GURL&)URL
                           injectionHandler:
-                              (ManualFillInjectionHandler*)injectionHandler {
+                              (ManualFillInjectionHandler*)injectionHandler
+                    invokedOnPasswordField:(BOOL)invokedOnPasswordField {
   self = [super initWithBaseViewController:viewController
                                    browser:browser
                           injectionHandler:injectionHandler];
@@ -61,10 +64,15 @@
     FaviconLoader* faviconLoader =
         IOSChromeFaviconLoaderFactory::GetForBrowserState(
             browser->GetBrowserState());
+    SyncSetupService* syncService = SyncSetupServiceFactory::GetForBrowserState(
+        self.browser->GetBrowserState());
 
     _passwordMediator = [[ManualFillPasswordMediator alloc]
-        initWithPasswordStore:passwordStore
-                faviconLoader:faviconLoader];
+         initWithPasswordStore:passwordStore
+                 faviconLoader:faviconLoader
+                      webState:browser->GetWebStateList()->GetActiveWebState()
+                   syncService:syncService
+        invokedOnPasswordField:invokedOnPasswordField];
     [_passwordMediator fetchPasswordsForURL:URL];
     _passwordMediator.actionSectionEnabled = YES;
     _passwordMediator.consumer = _passwordViewController;
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_mediator.h b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_mediator.h
index 150255c..38ac4b7 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_mediator.h
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_mediator.h
@@ -19,8 +19,13 @@
 class PasswordStore;
 }  // namespace password_manager
 
+namespace web {
+class WebState;
+}  // namespace web
+
 class FaviconLoader;
 class GURL;
+class SyncSetupService;
 
 namespace manual_fill {
 
@@ -62,6 +67,9 @@
                     (scoped_refptr<password_manager::PasswordStore>)
                         passwordStore
                         faviconLoader:(FaviconLoader*)faviconLoader
+                             webState:(web::WebState*)webState
+                          syncService:(SyncSetupService*)syncService
+               invokedOnPasswordField:(BOOL)invokedOnPasswordField
     NS_DESIGNATED_INITIALIZER;
 
 // Unavailable. Use |initWithPasswordStore:faviconLoader:|.
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_mediator.mm b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_mediator.mm
index eb9626a..1ab1eed0c 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_mediator.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_mediator.mm
@@ -8,9 +8,16 @@
 
 #include "base/metrics/user_metrics.h"
 #include "base/strings/sys_string_conversions.h"
+#import "components/autofill/ios/browser/autofill_util.h"
+#import "components/autofill/ios/form_util/form_activity_observer_bridge.h"
+#include "components/autofill/ios/form_util/form_activity_params.h"
 #include "components/password_manager/core/browser/password_store.h"
+#include "components/password_manager/core/common/password_manager_features.h"
+#import "components/password_manager/ios/password_generation_provider.h"
 #import "ios/chrome/browser/autofill/manual_fill/passwords_fetcher.h"
 #import "ios/chrome/browser/favicon/favicon_loader.h"
+#import "ios/chrome/browser/passwords/password_tab_helper.h"
+#import "ios/chrome/browser/sync/sync_setup_service.h"
 #import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_action_cell.h"
 #import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_content_injector.h"
 #import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_credential+PasswordForm.h"
@@ -21,6 +28,7 @@
 #import "ios/chrome/browser/ui/list_model/list_model.h"
 #import "ios/chrome/browser/ui/table_view/table_view_model.h"
 #include "ios/chrome/grit/ios_strings.h"
+#import "ios/web/public/web_state_observer_bridge.h"
 #include "ui/base/l10n/l10n_util_mac.h"
 #include "ui/gfx/favicon_size.h"
 #include "url/gurl.h"
@@ -53,7 +61,9 @@
       isEqualToString:credentials[secondIndex].host];
 }
 
-@interface ManualFillPasswordMediator () <ManualFillContentInjector,
+@interface ManualFillPasswordMediator () <CRWWebStateObserver,
+                                          FormActivityObserver,
+                                          ManualFillContentInjector,
                                           PasswordFetcherDelegate> {
   // The interface for getting and manipulating a user's saved passwords.
   scoped_refptr<password_manager::PasswordStore> _passwordStore;
@@ -72,23 +82,56 @@
 // YES if the password fetcher has completed at least one fetch.
 @property(nonatomic, assign) BOOL passwordFetcherDidFetch;
 
+// YES if the active field is of type 'password'.
+@property(nonatomic, assign) BOOL activeFieldIsPassword;
+
+// The relevant active web state.
+@property(nonatomic, assign) web::WebState* webState;
+
+// Sync setup service.
+@property(nonatomic, assign) SyncSetupService* syncService;
+
 @end
 
-@implementation ManualFillPasswordMediator
+@implementation ManualFillPasswordMediator {
+  // Bridge to observe the web state from Objective-C.
+  std::unique_ptr<web::WebStateObserverBridge> _webStateObserverBridge;
+
+  // Bridge to observe form activity in |_webState|.
+  std::unique_ptr<autofill::FormActivityObserverBridge>
+      _formActivityObserverBridge;
+}
 
 - (instancetype)initWithPasswordStore:
                     (scoped_refptr<password_manager::PasswordStore>)
                         passwordStore
-                        faviconLoader:(FaviconLoader*)faviconLoader {
+                        faviconLoader:(FaviconLoader*)faviconLoader
+                             webState:(web::WebState*)webState
+                          syncService:(SyncSetupService*)syncService
+               invokedOnPasswordField:(BOOL)invokedOnPasswordField {
   self = [super init];
   if (self) {
     _credentials = @[];
     _passwordStore = passwordStore;
     _faviconLoader = faviconLoader;
+    _webState = webState;
+    _syncService = syncService;
+    _activeFieldIsPassword = invokedOnPasswordField;
+    _webStateObserverBridge =
+        std::make_unique<web::WebStateObserverBridge>(self);
+    _webState->AddObserver(_webStateObserverBridge.get());
+    _formActivityObserverBridge =
+        std::make_unique<autofill::FormActivityObserverBridge>(_webState, self);
   }
   return self;
 }
 
+- (void)dealloc {
+  if (_webState) {
+    [self webStateDestroyed:_webState];
+  }
+}
+
 - (void)fetchPasswordsForURL:(const GURL&)URL {
   self.credentials = @[];
   self.passwordFetcher =
@@ -201,6 +244,23 @@
         manual_fill::OtherPasswordsAccessibilityIdentifier;
     [actions addObject:otherPasswordsItem];
 
+    if (base::FeatureList::IsEnabled(
+            password_manager::features::kEnableManualPasswordGeneration) &&
+        _syncService->IsSyncEnabled() && _activeFieldIsPassword) {
+      NSString* suggestPasswordTitleString = l10n_util::GetNSString(
+          IDS_IOS_MANUAL_FALLBACK_SUGGEST_PASSWORD_WITH_DOTS);
+      auto suggestPasswordItem = [[ManualFillActionItem alloc]
+          initWithTitle:suggestPasswordTitleString
+                 action:^{
+                   base::RecordAction(base::UserMetricsAction(
+                       "ManualFallback_Password_OpenSuggestPassword"));
+                   [weakSelf suggestPassword];
+                 }];
+      suggestPasswordItem.accessibilityIdentifier =
+          manual_fill::SuggestPasswordAccessibilityIdentifier;
+      [actions addObject:suggestPasswordItem];
+    }
+
     NSString* managePasswordsTitle =
         l10n_util::GetNSString(IDS_IOS_MANUAL_FALLBACK_MANAGE_PASSWORDS);
     auto managePasswordsItem = [[ManualFillActionItem alloc]
@@ -218,6 +278,17 @@
   }
 }
 
+- (void)suggestPassword {
+  if ([self canUserInjectInPasswordField:YES requiresHTTPS:NO]) {
+    DCHECK(_webState);
+    id<PasswordGenerationProvider> generationProvider =
+        PasswordTabHelper::FromWebState(_webState)
+            ->GetPasswordGenerationProvider();
+    if (generationProvider)
+      [generationProvider triggerPasswordGeneration];
+  }
+}
+
 #pragma mark - Setters
 
 - (void)setConsumer:(id<ManualFillPasswordConsumer>)consumer {
@@ -254,4 +325,29 @@
                                               completion);
 }
 
+#pragma mark - FormActivityObserver
+
+- (void)webState:(web::WebState*)webState
+    didRegisterFormActivity:(const autofill::FormActivityParams&)params
+                    inFrame:(web::WebFrame*)frame {
+  DCHECK_EQ(_webState, webState);
+  if (_activeFieldIsPassword !=
+      (params.field_type == autofill::kPasswordFieldType)) {
+    _activeFieldIsPassword = params.field_type == autofill::kPasswordFieldType;
+    [self postActionsToConsumer];
+  }
+}
+
+#pragma mark - CRWWebStateObserver
+
+- (void)webStateDestroyed:(web::WebState*)webState {
+  DCHECK_EQ(_webState, webState);
+  if (_webState) {
+    _webState->RemoveObserver(_webStateObserverBridge.get());
+    _webState = nullptr;
+  }
+  _webStateObserverBridge.reset();
+  _formActivityObserverBridge.reset();
+}
+
 @end
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
index fdcc7f33..1499d69 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
@@ -7,7 +7,10 @@
 #include "base/strings/utf_string_conversions.h"
 #import "base/test/ios/wait_util.h"
 #include "components/password_manager/core/browser/password_ui_utils.h"
+#include "components/password_manager/core/common/password_manager_features.h"
 #include "components/strings/grit/components_strings.h"
+#import "ios/chrome/browser/ui/authentication/signin_earl_grey.h"
+#import "ios/chrome/browser/ui/authentication/signin_earl_grey_ui.h"
 #import "ios/chrome/browser/ui/autofill/autofill_app_interface.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_actions.h"
@@ -35,6 +38,7 @@
 using chrome_test_util::ManualFallbackOtherPasswordsDismissMatcher;
 using chrome_test_util::ManualFallbackPasswordButtonMatcher;
 using chrome_test_util::ManualFallbackPasswordTableViewWindowMatcher;
+using chrome_test_util::ManualFallbackSuggestPasswordMatcher;
 using chrome_test_util::NavigationBarDoneButton;
 using chrome_test_util::NavigationBarCancelButton;
 using chrome_test_util::SettingsPasswordMatcher;
@@ -42,6 +46,7 @@
 using chrome_test_util::StaticTextWithAccessibilityLabelId;
 using chrome_test_util::TapWebElementWithId;
 using chrome_test_util::TapWebElementWithIdInFrame;
+using chrome_test_util::UseSuggestedPasswordMatcher;
 
 namespace {
 
@@ -104,6 +109,19 @@
 
 @implementation PasswordViewControllerTestCase
 
+- (AppLaunchConfiguration)appConfigurationForTestCase {
+  AppLaunchConfiguration config;
+  if ([self isRunningTest:@selector(testPasswordGenerationOnManualFallback)]) {
+    config.features_enabled.push_back(
+        password_manager::features::kEnableManualPasswordGeneration);
+  }
+  if ([self isRunningTest:@selector(testPasswordControllerKeepsRightSize)]) {
+    config.features_disabled.push_back(
+        password_manager::features::kEnableManualPasswordGeneration);
+  }
+  return config;
+}
+
 - (void)setUp {
   [super setUp];
   GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
@@ -640,4 +658,44 @@
       assertWithMatcher:grey_notVisible()];
 }
 
+// Tests password generation on manual fallback.
+- (void)testPasswordGenerationOnManualFallback {
+  [SigninEarlGreyUI signinWithFakeIdentity:[SigninEarlGrey fakeIdentity1]];
+  [ChromeEarlGrey waitForSyncInitialized:YES syncTimeout:10.0];
+
+  const GURL URL = self.testServer->GetURL(kFormHTMLFile);
+  [ChromeEarlGrey loadURL:URL];
+  [ChromeEarlGrey waitForWebStateContainingText:"hello!"];
+
+  // Bring up the keyboard.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+      performAction:TapWebElementWithId(kFormElementPassword)];
+
+  // Wait for the accessory icon to appear.
+  GREYAssertTrue([EarlGrey isKeyboardShownWithError:nil],
+                 @"Keyboard Should be Shown");
+
+  // Tap on the passwords icon.
+  [[EarlGrey selectElementWithMatcher:ManualFallbackPasswordIconMatcher()]
+      performAction:grey_tap()];
+
+  // Verify the password controller table view is visible.
+  [[EarlGrey selectElementWithMatcher:ManualFallbackPasswordTableViewMatcher()]
+      assertWithMatcher:grey_sufficientlyVisible()];
+
+  // Select a 'Suggest Password...' option.
+  [[EarlGrey selectElementWithMatcher:ManualFallbackSuggestPasswordMatcher()]
+      performAction:grey_tap()];
+
+  // Confirm by tapping on the 'Use Suggested Password' button.
+  [[EarlGrey selectElementWithMatcher:UseSuggestedPasswordMatcher()]
+      performAction:grey_tap()];
+
+  // Verify Web Content.
+  NSString* javaScriptCondition =
+      [NSString stringWithFormat:@"document.getElementById('%s').value !== ''",
+                                 kFormElementPassword];
+  XCTAssertTrue(WaitForJavaScriptCondition(javaScriptCondition));
+}
+
 @end
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
index 1ab8583..ec7edbc 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
@@ -349,7 +349,7 @@
   [self.sharingCoordinator stop];
   self.sharingCoordinator = nil;
   self.headerController = nil;
-  if (IsDiscoverFeedEnabled()) {
+  if (IsDiscoverFeedEnabled() && !IsRefactoredNTP()) {
     ios::GetChromeBrowserProvider()
         ->GetDiscoverFeedProvider()
         ->RemoveFeedViewController(self.discoverFeedViewController);
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
index 18aeb747..7662063 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
@@ -284,6 +284,11 @@
 - (void)stop {
   if (!self.started)
     return;
+  // Unfocus omnibox, to prevent it from lingering when it should be dismissed
+  // (for example, when navigating away or when changing feed visibility).
+  id<OmniboxCommands> omniboxCommandHandler =
+      HandlerForProtocol(self.browser->GetCommandDispatcher(), OmniboxCommands);
+  [omniboxCommandHandler cancelOmniboxEdit];
   self.viewPresented = NO;
   [self updateVisible];
   [self.contentSuggestionsCoordinator stop];
@@ -293,6 +298,12 @@
   self.contentSuggestionsCoordinator = nil;
   self.incognitoViewController = nil;
   self.ntpViewController = nil;
+  if (IsRefactoredNTP()) {
+    ios::GetChromeBrowserProvider()
+        ->GetDiscoverFeedProvider()
+        ->RemoveFeedViewController(
+            self.discoverFeedWrapperViewController.discoverFeed);
+  }
   self.discoverFeedWrapperViewController = nil;
 
   [self.ntpMediator shutdown];
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/save_address_profile/save_address_profile_infobar_banner_overlay_mediator.mm b/ios/chrome/browser/ui/overlays/infobar_banner/save_address_profile/save_address_profile_infobar_banner_overlay_mediator.mm
index c70d96d..a540a7d 100644
--- a/ios/chrome/browser/ui/overlays/infobar_banner/save_address_profile/save_address_profile_infobar_banner_overlay_mediator.mm
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/save_address_profile/save_address_profile_infobar_banner_overlay_mediator.mm
@@ -45,10 +45,7 @@
 
 #pragma mark - InfobarOverlayRequestMediator
 
-- (void)bannerInfobarButtonWasPressed:(UIButton*)sender {
-  // TODO(crbug.com/1167062): Set up modal view with the details.
-  [self dismissOverlay];
-}
+// TODO(crbug.com/1167062): Set up modal view with the details.
 
 @end
 
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm b/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
index 295fef12..c9a10579 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
@@ -77,8 +77,10 @@
 @property(nonatomic, readonly)
     TableViewModel<TableViewItem<ReadingListListItem>*>* tableViewModel;
 
-// Whether the UI is currently modifying the model.
-@property(nonatomic, assign) BOOL dataSourceBeingModifiedByUI;
+// The number of batch operation triggered by UI.
+// One UI operation can trigger multiple batch operation, so this can be greater
+// than 1.
+@property(nonatomic, assign) int numberOfBatchOperationInProgress;
 // Whether the data source has been modified while in editing mode.
 @property(nonatomic, assign) BOOL dataSourceModifiedWhileEditing;
 // The toolbar button manager.
@@ -371,7 +373,7 @@
 - (void)dataSourceChanged {
   // If the model is updated when the UI is already making a change, set a flag
   // to reload the data at the end of the editing.
-  if (self.dataSourceBeingModifiedByUI) {
+  if (self.numberOfBatchOperationInProgress) {
     self.dataSourceModifiedWhileEditing = YES;
   } else {
     [self reloadData];
@@ -591,12 +593,12 @@
 
 - (void)performBatchTableViewUpdates:(void (^)(void))updates
                           completion:(void (^)(BOOL finished))completion {
-  DCHECK(!self.dataSourceBeingModifiedByUI);
-  self.dataSourceBeingModifiedByUI = YES;
+  self.numberOfBatchOperationInProgress += 1;
   void (^releaseDataSource)(BOOL) = ^(BOOL finished) {
-    // Set dataSourceBeingModifiedByUI before calling completion, as completion
-    // may trigger another change.
-    self.dataSourceBeingModifiedByUI = NO;
+    // Set numberOfBatchOperationInProgress before calling completion, as
+    // completion may trigger another change.
+    DCHECK_GT(self.numberOfBatchOperationInProgress, 0);
+    self.numberOfBatchOperationInProgress -= 1;
     if (completion) {
       completion(finished);
     }
diff --git a/ios/chrome/browser/web/java_script_console/java_script_console_feature_unittest.mm b/ios/chrome/browser/web/java_script_console/java_script_console_feature_unittest.mm
index 830639e6..1547ac7 100644
--- a/ios/chrome/browser/web/java_script_console/java_script_console_feature_unittest.mm
+++ b/ios/chrome/browser/web/java_script_console/java_script_console_feature_unittest.mm
@@ -98,7 +98,18 @@
 class JavaScriptConsoleFeatureTest : public ChromeWebTest {
  protected:
   JavaScriptConsoleFeatureTest()
-      : ChromeWebTest(std::make_unique<web::FakeWebClient>()) {}
+      : ChromeWebTest(std::make_unique<web::FakeWebClient>()) {
+    JavaScriptConsoleFeature* feature =
+        JavaScriptConsoleFeatureFactory::GetInstance()->GetForBrowserState(
+            GetBrowserState());
+    feature->SetDelegate(&delegate_);
+    GetWebClient()->SetJavaScriptFeatures({feature});
+  }
+
+  void SetUp() override {
+    ChromeWebTest::SetUp();
+    ConfigureJavaScriptFeatures();
+  }
 
   web::FakeWebClient* GetWebClient() override {
     return static_cast<web::FakeWebClient*>(
@@ -127,22 +138,11 @@
     return iframe;
   }
 
-  // Attaches a JavaScriptConsoleFeature instance to the associated browser
-  // state with |delegate_| configured to receive console message details.
-  void SetupFeature() {
-    JavaScriptConsoleFeature* feature =
-        JavaScriptConsoleFeatureFactory::GetInstance()->GetForBrowserState(
-            GetBrowserState());
-    feature->SetDelegate(&delegate_);
-    GetWebClient()->SetJavaScriptFeatures({feature});
-  }
-
   FakeJavaScriptConsoleFeatureDelegate delegate_;
 };
 
 // Tests that debug console message details are received from the main frame.
 TEST_F(JavaScriptConsoleFeatureTest, DebugMessageReceivedMainFrame) {
-  SetupFeature();
   ASSERT_TRUE(IsDelegateStateEmpty());
 
   ASSERT_TRUE(LoadHtml(kPageHtml));
@@ -159,7 +159,6 @@
 
 // Tests that error console message details are received from the main frame.
 TEST_F(JavaScriptConsoleFeatureTest, ErrorMessageReceivedMainFrame) {
-  SetupFeature();
   ASSERT_TRUE(IsDelegateStateEmpty());
 
   ASSERT_TRUE(LoadHtml(kPageHtml));
@@ -176,7 +175,6 @@
 
 // Tests that info console message details are received from the main frame.
 TEST_F(JavaScriptConsoleFeatureTest, InfoMessageReceivedMainFrame) {
-  SetupFeature();
   ASSERT_TRUE(IsDelegateStateEmpty());
 
   ASSERT_TRUE(LoadHtml(kPageHtml));
@@ -193,7 +191,6 @@
 
 // Tests that log console message details are received from the main frame.
 TEST_F(JavaScriptConsoleFeatureTest, LogMessageReceivedMainFrame) {
-  SetupFeature();
   ASSERT_TRUE(IsDelegateStateEmpty());
 
   ASSERT_TRUE(LoadHtml(kPageHtml));
@@ -210,7 +207,6 @@
 
 // Tests that warning console message details are received from the main frame.
 TEST_F(JavaScriptConsoleFeatureTest, WarnMessageReceivedMainFrame) {
-  SetupFeature();
   ASSERT_TRUE(IsDelegateStateEmpty());
 
   ASSERT_TRUE(LoadHtml(kPageHtml));
@@ -227,7 +223,6 @@
 
 // Tests that debug console message details are received from an iframe.
 TEST_F(JavaScriptConsoleFeatureTest, DebugMessageReceivedIFrame) {
-  SetupFeature();
   ASSERT_TRUE(IsDelegateStateEmpty());
 
   ASSERT_TRUE(LoadHtml(kIFramePageHtml));
@@ -246,7 +241,6 @@
 
 // Tests that error console message details are received from an iframe.
 TEST_F(JavaScriptConsoleFeatureTest, ErrorMessageReceivedIFrame) {
-  SetupFeature();
   ASSERT_TRUE(IsDelegateStateEmpty());
 
   ASSERT_TRUE(LoadHtml(kIFramePageHtml));
@@ -265,7 +259,6 @@
 
 // Tests that info console message details are received from an iframe.
 TEST_F(JavaScriptConsoleFeatureTest, InfoMessageReceivedIFrame) {
-  SetupFeature();
   ASSERT_TRUE(IsDelegateStateEmpty());
 
   ASSERT_TRUE(LoadHtml(kIFramePageHtml));
@@ -284,7 +277,6 @@
 
 // Tests that log console message details are received from an iframe.
 TEST_F(JavaScriptConsoleFeatureTest, LogMessageReceivedIFrame) {
-  SetupFeature();
   ASSERT_TRUE(IsDelegateStateEmpty());
 
   ASSERT_TRUE(LoadHtml(kIFramePageHtml));
@@ -303,7 +295,6 @@
 
 // Tests that warning console message details are received from an iframe.
 TEST_F(JavaScriptConsoleFeatureTest, WarnMessageReceivedIFrame) {
-  SetupFeature();
   ASSERT_TRUE(IsDelegateStateEmpty());
 
   ASSERT_TRUE(LoadHtml(kIFramePageHtml));
diff --git a/ios/chrome/content_widget_extension/BUILD.gn b/ios/chrome/content_widget_extension/BUILD.gn
index aebfb8c..fbbd460 100644
--- a/ios/chrome/content_widget_extension/BUILD.gn
+++ b/ios/chrome/content_widget_extension/BUILD.gn
@@ -2,10 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/apple/compile_entitlements.gni")
 import("//build/apple/tweak_info_plist.gni")
 import("//build/config/ios/rules.gni")
 import("//build/config/locales.gni")
+import("//build/config/mac/base_rules.gni")
 import("//ios/build/chrome_build.gni")
 import("//ios/build/config.gni")
 import("//ios/chrome/tools/strings/generate_localizable_strings.gni")
diff --git a/ios/chrome/content_widget_extension/strings/BUILD.gn b/ios/chrome/content_widget_extension/strings/BUILD.gn
index 6ced07c0..cfcef00 100644
--- a/ios/chrome/content_widget_extension/strings/BUILD.gn
+++ b/ios/chrome/content_widget_extension/strings/BUILD.gn
@@ -16,7 +16,7 @@
   source = "ios_content_widget_extension_chromium_strings.grd"
   output_dir = "$root_gen_dir/ios/content_widget_extension"
   outputs = [ "grit/ios_content_widget_extension_chromium_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "ios_content_widget_extension_chromium_strings_$locale.pak" ]
   }
 }
@@ -25,7 +25,7 @@
   source = "ios_content_widget_extension_google_chrome_strings.grd"
   output_dir = "$root_gen_dir/ios/content_widget_extension"
   outputs = [ "grit/ios_content_widget_extension_google_chrome_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs +=
         [ "ios_content_widget_extension_google_chrome_strings_$locale.pak" ]
   }
diff --git a/ios/chrome/credential_provider_extension/BUILD.gn b/ios/chrome/credential_provider_extension/BUILD.gn
index dbfac33..20e5944 100644
--- a/ios/chrome/credential_provider_extension/BUILD.gn
+++ b/ios/chrome/credential_provider_extension/BUILD.gn
@@ -2,10 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/apple/compile_entitlements.gni")
 import("//build/apple/tweak_info_plist.gni")
 import("//build/config/ios/rules.gni")
 import("//build/config/locales.gni")
+import("//build/config/mac/base_rules.gni")
 import("//ios/build/chrome_build.gni")
 import("//ios/build/config.gni")
 import("//ios/chrome/extension_repack.gni")
diff --git a/ios/chrome/credential_provider_extension/strings/BUILD.gn b/ios/chrome/credential_provider_extension/strings/BUILD.gn
index 9f5b3c0f..660b878 100644
--- a/ios/chrome/credential_provider_extension/strings/BUILD.gn
+++ b/ios/chrome/credential_provider_extension/strings/BUILD.gn
@@ -9,7 +9,7 @@
   source = "ios_credential_provider_extension_strings.grd"
   output_dir = "$root_gen_dir/ios/credential_provider_extension"
   outputs = [ "grit/ios_credential_provider_extension_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "ios_credential_provider_extension_strings_$locale.pak" ]
   }
 }
diff --git a/ios/chrome/search_widget_extension/BUILD.gn b/ios/chrome/search_widget_extension/BUILD.gn
index 447b558..e05fce0c 100644
--- a/ios/chrome/search_widget_extension/BUILD.gn
+++ b/ios/chrome/search_widget_extension/BUILD.gn
@@ -2,10 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/apple/compile_entitlements.gni")
 import("//build/apple/tweak_info_plist.gni")
 import("//build/config/ios/rules.gni")
 import("//build/config/locales.gni")
+import("//build/config/mac/base_rules.gni")
 import("//ios/build/chrome_build.gni")
 import("//ios/build/config.gni")
 import("//ios/chrome/tools/strings/generate_localizable_strings.gni")
diff --git a/ios/chrome/search_widget_extension/strings/BUILD.gn b/ios/chrome/search_widget_extension/strings/BUILD.gn
index 4e7fa47..c3ff3d3 100644
--- a/ios/chrome/search_widget_extension/strings/BUILD.gn
+++ b/ios/chrome/search_widget_extension/strings/BUILD.gn
@@ -17,7 +17,7 @@
   source = "ios_search_widget_extension_strings.grd"
   output_dir = "$root_gen_dir/ios/search_widget_extension"
   outputs = [ "grit/ios_search_widget_extension_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "ios_search_widget_extension_strings_$locale.pak" ]
   }
 }
@@ -26,7 +26,7 @@
   source = "ios_search_widget_extension_chromium_strings.grd"
   output_dir = "$root_gen_dir/ios/search_widget_extension"
   outputs = [ "grit/ios_search_widget_extension_chromium_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "ios_search_widget_extension_chromium_strings_$locale.pak" ]
   }
 }
@@ -35,7 +35,7 @@
   source = "ios_search_widget_extension_google_chrome_strings.grd"
   output_dir = "$root_gen_dir/ios/search_widget_extension"
   outputs = [ "grit/ios_search_widget_extension_google_chrome_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs +=
         [ "ios_search_widget_extension_google_chrome_strings_$locale.pak" ]
   }
diff --git a/ios/chrome/share_extension/BUILD.gn b/ios/chrome/share_extension/BUILD.gn
index 084531d..4caef1d7 100644
--- a/ios/chrome/share_extension/BUILD.gn
+++ b/ios/chrome/share_extension/BUILD.gn
@@ -2,10 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/apple/compile_entitlements.gni")
 import("//build/apple/tweak_info_plist.gni")
 import("//build/config/ios/rules.gni")
 import("//build/config/locales.gni")
+import("//build/config/mac/base_rules.gni")
 import("//ios/build/chrome_build.gni")
 import("//ios/build/config.gni")
 import("//ios/chrome/extension_repack.gni")
diff --git a/ios/chrome/share_extension/strings/BUILD.gn b/ios/chrome/share_extension/strings/BUILD.gn
index 8cc05c1..583e71b8 100644
--- a/ios/chrome/share_extension/strings/BUILD.gn
+++ b/ios/chrome/share_extension/strings/BUILD.gn
@@ -9,7 +9,7 @@
   source = "ios_share_extension_strings.grd"
   output_dir = "$root_gen_dir/ios/share_extension"
   outputs = [ "grit/ios_share_extension_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "ios_share_extension_strings_$locale.pak" ]
   }
 }
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.h b/ios/chrome/test/earl_grey/chrome_matchers.h
index bce4ad88..31079cb 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.h
+++ b/ios/chrome/test/earl_grey/chrome_matchers.h
@@ -546,6 +546,13 @@
 // Returns the matcher for the iOS 13+ Activity View header.
 id<GREYMatcher> ActivityViewHeader(NSString* page_title);
 
+// Returns a matcher for the button to trigger password generation on manual
+// fallback.
+id<GREYMatcher> ManualFallbackSuggestPasswordMatcher();
+
+// Returns a matcher for the button to accept the generated password.
+id<GREYMatcher> UseSuggestedPasswordMatcher();
+
 }  // namespace chrome_test_util
 
 #endif  // IOS_CHROME_TEST_EARL_GREY_CHROME_MATCHERS_H_
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.mm b/ios/chrome/test/earl_grey/chrome_matchers.mm
index ac0bc3f2..2e91c8e 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers.mm
@@ -687,4 +687,12 @@
   return [ChromeMatchersAppInterface activityViewHeaderWithTitle:page_title];
 }
 
+id<GREYMatcher> ManualFallbackSuggestPasswordMatcher() {
+  return [ChromeMatchersAppInterface manualFallbackSuggestPasswordMatcher];
+}
+
+id<GREYMatcher> UseSuggestedPasswordMatcher() {
+  return [ChromeMatchersAppInterface useSuggestedPasswordMatcher];
+}
+
 }  // namespace chrome_test_util
diff --git a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
index 1782151..49c2216 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
@@ -537,6 +537,13 @@
 // Returns the matcher for the Activity View header.
 + (id<GREYMatcher>)activityViewHeaderWithTitle:(NSString*)pageTitle;
 
+// Returns a matcher for the button to trigger password generation on manual
+// fallback.
++ (id<GREYMatcher>)manualFallbackSuggestPasswordMatcher;
+
+// Returns a matcher for the button to accept the generated password.
++ (id<GREYMatcher>)useSuggestedPasswordMatcher;
+
 @end
 
 #endif  // IOS_CHROME_TEST_EARL_GREY_CHROME_MATCHERS_APP_INTERFACE_H_
diff --git a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
index b31f0d97..65e4bb4 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
@@ -1040,4 +1040,15 @@
                     nil);
 }
 
++ (id<GREYMatcher>)manualFallbackSuggestPasswordMatcher {
+  return grey_accessibilityID(
+      manual_fill::SuggestPasswordAccessibilityIdentifier);
+}
+
++ (id<GREYMatcher>)useSuggestedPasswordMatcher {
+  return grey_allOf(
+      [self buttonWithAccessibilityLabelID:IDS_IOS_USE_SUGGESTED_PASSWORD],
+      grey_interactable(), nullptr);
+}
+
 @end
diff --git a/ios/chrome/test/earl_grey2/chrome_ios_eg2_test.gni b/ios/chrome/test/earl_grey2/chrome_ios_eg2_test.gni
index fe44817..ed56389 100644
--- a/ios/chrome/test/earl_grey2/chrome_ios_eg2_test.gni
+++ b/ios/chrome/test/earl_grey2/chrome_ios_eg2_test.gni
@@ -2,7 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/apple/compile_entitlements.gni")
 import("//build/apple/tweak_info_plist.gni")
 import("//build/config/ios/rules.gni")
 import("//ios/build/chrome_build.gni")
diff --git a/ios/chrome/widget_kit_extension/BUILD.gn b/ios/chrome/widget_kit_extension/BUILD.gn
index d6726e29..c2f9c05 100644
--- a/ios/chrome/widget_kit_extension/BUILD.gn
+++ b/ios/chrome/widget_kit_extension/BUILD.gn
@@ -2,10 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/apple/compile_entitlements.gni")
 import("//build/apple/tweak_info_plist.gni")
 import("//build/config/ios/rules.gni")
 import("//build/config/locales.gni")
+import("//build/config/mac/base_rules.gni")
 import("//ios/build/chrome_build.gni")
 import("//ios/build/config.gni")
 import("//ios/chrome/extension_repack.gni")
diff --git a/ios/chrome/widget_kit_extension/strings/BUILD.gn b/ios/chrome/widget_kit_extension/strings/BUILD.gn
index 9b2cdbb..3ec7de22 100644
--- a/ios/chrome/widget_kit_extension/strings/BUILD.gn
+++ b/ios/chrome/widget_kit_extension/strings/BUILD.gn
@@ -9,7 +9,7 @@
   source = "ios_widget_kit_extension_strings.grd"
   output_dir = "$root_gen_dir/ios/widget_kit_extension"
   outputs = [ "grit/ios_widget_kit_extension_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "ios_widget_kit_extension_strings_$locale.pak" ]
   }
 }
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 2a8ba5fb..12d4f2a 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 @@
-c03fbef882f011134d42819e9769b4fd4d2d651b
\ No newline at end of file
+ea28072ba40610a3f84ec618562ac20e966019a3
\ 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 e24ff9b..4519219 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 @@
-09bfc82c80777eb244c499fcf83bf7c8422b6ece
\ No newline at end of file
+c70c332192f209b984b939f88166674db7d32e84
\ 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 c6d5f2d..24695d6 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 @@
-d6ab47ed6d3d397ac3c121e4d2c7805875afc8ed
\ No newline at end of file
+b067b2a1fcc529843f6a920f60dcf1b0fbc674b7
\ 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 32d2770..10ac9b7 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 @@
-502cf6556751ae9d92e8d17636e71135ba171fe0
\ No newline at end of file
+7003d9d7ab8ce86586427f33a56a76a2e9d12483
\ 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 2cdbaf5..ada1e36 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 @@
-2798974c874e7ce97df9568a45d0251a42630806
\ No newline at end of file
+7ba5c08e3811282ed0cc01240b73cdda95a90d23
\ 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 6c2824d2..44bcb382 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 @@
-31c8bfac1ff89f37fa364e4d31ff4c42c7415508
\ No newline at end of file
+9be78a5452f11f8ff03f631101f9f29f717413bb
\ 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 c6c0cefd..b8bd251e 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 @@
-019db256f3543ea5e903de22998944058327ed84
\ No newline at end of file
+31fae7915fbe361eea2c529ecf132b82251a40ff
\ 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 5de93cb..46e05ac 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 @@
-0f791a2d2615d3e786f5c566c519f67e5b68e960
\ No newline at end of file
+b58f64d3f82dead980e411b88ca1911bff23dd37
\ 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 b6056e66..b3c25870 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 @@
-1794168705d04369f4348de5c032c53baf3bfae0
\ No newline at end of file
+0fc7f0569cb87a9cd286cace77150806b5d72dbe
\ 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 17010bca..dd17545 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 @@
-d01345743e4cea39d4a65e470f97174c64b41105
\ No newline at end of file
+5efc072914a5f69776865d26b0172085ab99eb06
\ No newline at end of file
diff --git a/ios/web/BUILD.gn b/ios/web/BUILD.gn
index c2edca829..6ff017c 100644
--- a/ios/web/BUILD.gn
+++ b/ios/web/BUILD.gn
@@ -438,7 +438,6 @@
     "//ios/net",
     "//ios/testing:ocmock_support",
     "//ios/web/common:web_view_creation_util",
-    "//ios/web/find_in_page",
     "//ios/web/js_features/context_menu",
     "//ios/web/js_messaging",
     "//ios/web/public",
@@ -459,7 +458,6 @@
 
   sources = [
     "web_state/js/common_js_unittest.mm",
-    "web_state/js/find_in_page_js_unittest.mm",
     "web_state/js/message_js_unittest.mm",
   ]
 }
@@ -559,8 +557,10 @@
     "//ios/web/common:web_view_creation_util",
     "//ios/web/download:download_inttests",
     "//ios/web/favicon:inttests",
+    "//ios/web/find_in_page",
     "//ios/web/js_features/context_menu",
     "//ios/web/js_messaging:inttests",
+    "//ios/web/js_messaging:java_script_feature",
     "//ios/web/js_messaging:java_script_feature_util",
     "//ios/web/navigation",
     "//ios/web/navigation:core",
@@ -636,7 +636,6 @@
   sources = [
     "web_state/js/resources/all_frames_web_bundle.js",
     "web_state/js/resources/cookie.js",
-    "web_state/js/resources/find_in_page.js",
     "web_state/js/resources/share_workaround.js",
   ]
 }
diff --git a/ios/web/find_in_page/BUILD.gn b/ios/web/find_in_page/BUILD.gn
index 41665c5..869794c 100644
--- a/ios/web/find_in_page/BUILD.gn
+++ b/ios/web/find_in_page/BUILD.gn
@@ -2,8 +2,12 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//ios/web/js_compile.gni")
+
 source_set("find_in_page") {
   deps = [
+    ":find_in_page_event_listeners_js",
+    ":find_in_page_js",
     "//base",
     "//ios/web/js_messaging",
     "//ios/web/public/",
@@ -15,6 +19,8 @@
   sources = [
     "find_in_page_constants.h",
     "find_in_page_constants.mm",
+    "find_in_page_java_script_feature.h",
+    "find_in_page_java_script_feature.mm",
     "find_in_page_manager_delegate_bridge.mm",
     "find_in_page_manager_impl.h",
     "find_in_page_manager_impl.mm",
@@ -25,21 +31,40 @@
   configs += [ "//build/config/compiler:enable_arc" ]
 }
 
+js_compile_bundle("find_in_page_js") {
+  visibility = [ ":find_in_page" ]
+  closure_entry_point = "__crWeb.findInPage"
+
+  sources = [ "resources/find_in_page.js" ]
+}
+
+js_compile_bundle("find_in_page_event_listeners_js") {
+  visibility = [ ":find_in_page" ]
+  closure_entry_point = "__crWeb.findInPageEventListeners"
+
+  sources = [ "resources/find_in_page_event_listeners.js" ]
+}
+
 source_set("find_in_page_unittests") {
   testonly = true
   deps = [
     ":find_in_page",
     "//base",
     "//base/test:test_support",
+    "//ios/web/js_messaging",
+    "//ios/web/js_messaging:java_script_feature",
     "//ios/web/public",
     "//ios/web/public/find_in_page",
     "//ios/web/public/js_messaging",
     "//ios/web/public/test",
     "//ios/web/public/test/fakes",
+    "//ios/web/test/fakes",
+    "//ios/web/web_state/ui:wk_web_view_configuration_provider_header",
     "//testing/gtest",
   ]
 
   sources = [
+    "find_in_page_js_unittest.mm",
     "find_in_page_manager_delegate_bridge_unittest.mm",
     "find_in_page_manger_impl_unittest.mm",
     "find_in_page_request_unittest.mm",
diff --git a/ios/web/find_in_page/find_in_page_java_script_feature.h b/ios/web/find_in_page/find_in_page_java_script_feature.h
new file mode 100644
index 0000000..f08ff42
--- /dev/null
+++ b/ios/web/find_in_page/find_in_page_java_script_feature.h
@@ -0,0 +1,73 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_WEB_FIND_IN_PAGE_FIND_IN_PAGE_JAVA_SCRIPT_FEATURE_H_
+#define IOS_WEB_FIND_IN_PAGE_FIND_IN_PAGE_JAVA_SCRIPT_FEATURE_H_
+
+#include "base/callback.h"
+#include "base/no_destructor.h"
+#include "base/values.h"
+#include "ios/web/public/js_messaging/java_script_feature.h"
+
+namespace web {
+
+class WebFrame;
+
+namespace find_in_page {
+// Value returned when a |Search| or |Pump| call times out.
+extern const int kFindInPagePending;
+}  // namespace find_in_page
+
+// A feature which searches for, highlights and allows navigating through text
+// search results.
+class FindInPageJavaScriptFeature : public JavaScriptFeature {
+ public:
+  // This feature holds no state, so only a single static instance is ever
+  // needed.
+  static FindInPageJavaScriptFeature* GetInstance();
+
+  // Searches for string |query| in |frame|. |callback| returns the number of
+  // search results found or |kFindInPagePending| if a call to |Pump| is
+  // necessary before match count is available.
+  bool Search(WebFrame* frame,
+              const std::string& query,
+              base::OnceCallback<void(base::Optional<int>)> callback);
+
+  // Continues an ongoing search started with |Search| which hasn't yet
+  // completed. |callback| returns the number of search results found or
+  // |kFindInPagePending| if more calls to |Pump| are necessary before match
+  // count is available.
+  void Pump(WebFrame* frame,
+            base::OnceCallback<void(base::Optional<int>)> callback);
+
+  // Selects the given match at |index| in |frame|. |callback| is called with
+  // the dictionary value results from the selection.
+  void SelectMatch(WebFrame* frame,
+                   int index,
+                   base::OnceCallback<void(const base::Value*)> callback);
+
+  // Stops any current search and clears highlighted/selected state from the
+  // page.
+  void Stop(WebFrame* frame);
+
+ private:
+  friend class base::NoDestructor<FindInPageJavaScriptFeature>;
+
+  // Processes the JavaScript |result| to extract the match count and send it
+  // to |callback|.
+  void ProcessSearchResult(
+      base::OnceCallback<void(const base::Optional<int>)> callback,
+      const base::Value* result);
+
+  FindInPageJavaScriptFeature();
+  ~FindInPageJavaScriptFeature() override;
+
+  FindInPageJavaScriptFeature(const FindInPageJavaScriptFeature&) = delete;
+  FindInPageJavaScriptFeature& operator=(const FindInPageJavaScriptFeature&) =
+      delete;
+};
+
+}  // namespace web
+
+#endif  // IOS_WEB_FIND_IN_PAGE_FIND_IN_PAGE_JAVA_SCRIPT_FEATURE_H_
diff --git a/ios/web/find_in_page/find_in_page_java_script_feature.mm b/ios/web/find_in_page/find_in_page_java_script_feature.mm
new file mode 100644
index 0000000..a3f2466
--- /dev/null
+++ b/ios/web/find_in_page/find_in_page_java_script_feature.mm
@@ -0,0 +1,115 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/web/find_in_page/find_in_page_java_script_feature.h"
+
+#import "ios/web/find_in_page/find_in_page_constants.h"
+#include "ios/web/public/js_messaging/java_script_feature_util.h"
+#include "ios/web/public/js_messaging/web_frame.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+const char kScriptName[] = "find_in_page_js";
+const char kEventListenersScriptName[] = "find_in_page_event_listeners_js";
+
+// Timeout for the find within JavaScript in milliseconds.
+const double kFindInPageFindTimeout = 100.0;
+
+// The timeout for JavaScript function calls in milliseconds. Important that
+// this is longer than |kFindInPageFindTimeout| to allow for incomplete find to
+// restart again. If this timeout hits, then something went wrong with the find
+// and find in page should not continue.
+const double kJavaScriptFunctionCallTimeout = 200.0;
+}  // namespace
+
+namespace web {
+
+namespace find_in_page {
+
+const int kFindInPagePending = -1;
+
+}  // namespace find_in_page
+
+// static
+FindInPageJavaScriptFeature* FindInPageJavaScriptFeature::GetInstance() {
+  static base::NoDestructor<FindInPageJavaScriptFeature> instance;
+  return instance.get();
+}
+
+FindInPageJavaScriptFeature::FindInPageJavaScriptFeature()
+    : JavaScriptFeature(
+          ContentWorld::kAnyContentWorld,
+          {FeatureScript::CreateWithFilename(
+               kScriptName,
+               FeatureScript::InjectionTime::kDocumentStart,
+               FeatureScript::TargetFrames::kAllFrames,
+               FeatureScript::ReinjectionBehavior::kInjectOncePerWindow),
+           FeatureScript::CreateWithFilename(
+               kEventListenersScriptName,
+               FeatureScript::InjectionTime::kDocumentStart,
+               FeatureScript::TargetFrames::kAllFrames,
+               FeatureScript::ReinjectionBehavior::
+                   kReinjectOnDocumentRecreation)},
+          {web::java_script_features::GetBaseJavaScriptFeature()}) {}
+
+FindInPageJavaScriptFeature::~FindInPageJavaScriptFeature() = default;
+
+bool FindInPageJavaScriptFeature::Search(
+    WebFrame* frame,
+    const std::string& query,
+    base::OnceCallback<void(base::Optional<int>)> callback) {
+  std::vector<base::Value> params;
+  params.push_back(base::Value(query));
+  params.push_back(base::Value(kFindInPageFindTimeout));
+  return CallJavaScriptFunction(
+      frame, kFindInPageSearch, params,
+      base::BindOnce(&FindInPageJavaScriptFeature::ProcessSearchResult,
+                     base::Unretained(GetInstance()), std::move(callback)),
+      base::TimeDelta::FromMilliseconds(kJavaScriptFunctionCallTimeout));
+}
+
+void FindInPageJavaScriptFeature::Pump(
+    WebFrame* frame,
+    base::OnceCallback<void(base::Optional<int>)> callback) {
+  std::vector<base::Value> params;
+  params.push_back(base::Value(kFindInPageFindTimeout));
+  CallJavaScriptFunction(
+      frame, kFindInPagePump, params,
+      base::BindOnce(&FindInPageJavaScriptFeature::ProcessSearchResult,
+                     base::Unretained(GetInstance()), std::move(callback)),
+      base::TimeDelta::FromMilliseconds(kJavaScriptFunctionCallTimeout));
+}
+
+void FindInPageJavaScriptFeature::SelectMatch(
+    WebFrame* frame,
+    int index,
+    base::OnceCallback<void(const base::Value*)> callback) {
+  std::vector<base::Value> params;
+  params.push_back(base::Value(index));
+  CallJavaScriptFunction(
+      frame, kFindInPageSelectAndScrollToMatch, params, std::move(callback),
+      base::TimeDelta::FromMilliseconds(kJavaScriptFunctionCallTimeout));
+}
+
+void FindInPageJavaScriptFeature::Stop(WebFrame* frame) {
+  std::vector<base::Value> params;
+  CallJavaScriptFunction(frame, kFindInPageStop, params);
+}
+
+void FindInPageJavaScriptFeature::ProcessSearchResult(
+    base::OnceCallback<void(const base::Optional<int>)> callback,
+    const base::Value* result) {
+  base::Optional<int> match_count;
+  if (result && result->is_double()) {
+    // Valid match number returned. If not, match count will be 0 in order to
+    // zero-out count from previous find.
+    match_count = static_cast<int>(result->GetDouble());
+  }
+  std::move(callback).Run(match_count);
+}
+
+}  // namespace web
diff --git a/ios/web/web_state/js/find_in_page_js_unittest.mm b/ios/web/find_in_page/find_in_page_js_unittest.mm
similarity index 78%
rename from ios/web/web_state/js/find_in_page_js_unittest.mm
rename to ios/web/find_in_page/find_in_page_js_unittest.mm
index 05f1032..760e411 100644
--- a/ios/web/web_state/js/find_in_page_js_unittest.mm
+++ b/ios/web/find_in_page/find_in_page_js_unittest.mm
@@ -3,18 +3,25 @@
 // found in the LICENSE file.
 
 #import <UIKit/UIKit.h>
+#import <WebKit/WebKit.h>
 
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/run_loop.h"
 #import "base/test/ios/wait_util.h"
 #import "ios/web/find_in_page/find_in_page_constants.h"
+#import "ios/web/find_in_page/find_in_page_java_script_feature.h"
+#import "ios/web/js_messaging/java_script_feature_manager.h"
+#include "ios/web/js_messaging/web_frame_impl.h"
 #import "ios/web/public/js_messaging/web_frame.h"
 #import "ios/web/public/js_messaging/web_frames_manager.h"
+#import "ios/web/public/test/fakes/fake_web_client.h"
+#import "ios/web/public/test/js_test_util.h"
 #import "ios/web/public/test/web_test_with_web_state.h"
 #import "ios/web/public/ui/crw_web_view_proxy.h"
 #import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h"
 #import "ios/web/public/web_state.h"
+#import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
 #include "testing/gtest_mac.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -33,7 +40,7 @@
 
 // Pump search timeout in milliseconds.
 const double kPumpSearchTimeout = 100.0;
-}
+}  // namespace
 
 namespace web {
 
@@ -41,32 +48,66 @@
 // correct.
 class FindInPageJsTest : public WebTestWithWebState {
  protected:
+  FindInPageJsTest() : WebTestWithWebState(std::make_unique<FakeWebClient>()) {
+    GetWebClient()->SetJavaScriptFeatures(
+        {FindInPageJavaScriptFeature::GetInstance()});
+  }
+
+  web::FakeWebClient* GetWebClient() override {
+    return static_cast<web::FakeWebClient*>(
+        WebTestWithWebState::GetWebClient());
+  }
+
+  void SetUp() override {
+    WebTestWithWebState::SetUp();
+
+    ConfigureJavaScriptFeatures();
+
+    content_world_ =
+        JavaScriptFeatureManager::FromBrowserState(GetBrowserState())
+            ->GetContentWorldForFeature(
+                FindInPageJavaScriptFeature::GetInstance());
+  }
+
+  bool WaitForWebFramesCount(unsigned long web_frames_count) {
+    return WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
+      return all_web_frames().size() == web_frames_count;
+    });
+  }
+
   // Returns WebFramesManager instance.
-  std::set<WebFrame*> all_web_frames() {
-    return web_state()->GetWebFramesManager()->GetAllWebFrames();
+  std::set<WebFrameImpl*> all_web_frames() {
+    std::set<WebFrameImpl*> frames;
+    for (WebFrame* frame :
+         web_state()->GetWebFramesManager()->GetAllWebFrames()) {
+      frames.insert(static_cast<WebFrameImpl*>(frame));
+    }
+    return frames;
   }
   // Returns main frame for |web_state_|.
-  WebFrame* main_web_frame() {
-    return web_state()->GetWebFramesManager()->GetMainWebFrame();
+  WebFrameImpl* main_web_frame() {
+    return static_cast<WebFrameImpl*>(
+        web_state()->GetWebFramesManager()->GetMainWebFrame());
   }
+
+  JavaScriptContentWorld* content_world_;
 };
 
 // Tests that FindInPage searches in main frame containing a match and responds
 // with 1 match.
 TEST_F(FindInPageJsTest, FindText) {
   ASSERT_TRUE(LoadHtml("<span>foo</span>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
-
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value(kFindStringFoo));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         double count = result->GetDouble();
@@ -84,17 +125,17 @@
 // hidden and responds with 0 matches.
 TEST_F(FindInPageJsTest, FindTextNoResults) {
   ASSERT_TRUE(LoadHtml("<span style='display:none'>foo</span>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value(kFindStringFoo));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         double count = result->GetDouble();
@@ -110,17 +151,16 @@
 // Tests that FindInPage searches in child iframe and asserts that a result was
 // found.
 TEST_F(FindInPageJsTest, FindIFrameText) {
-  ASSERT_TRUE(LoadHtml(
+  ASSERT_TRUE(WebTestWithWebState::LoadHtml(
       "<iframe "
       "srcdoc='<html><body><span>foo</span></body></html>'></iframe>"));
+  ASSERT_TRUE(WaitForWebFramesCount(2));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 2;
-  }));
-  std::set<WebFrame*> all_frames = all_web_frames();
+  std::set<WebFrameImpl*> all_frames = all_web_frames();
   __block bool message_received = false;
-  WebFrame* child_frame = nullptr;
+  WebFrameImpl* child_frame = nullptr;
   for (auto* frame : all_frames) {
     if (frame->IsMainFrame()) {
       continue;
@@ -131,8 +171,9 @@
   std::vector<base::Value> params;
   params.push_back(base::Value(kFindStringFoo));
   params.push_back(base::Value(kPumpSearchTimeout));
-  child_frame->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  child_frame->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         double count = result->GetDouble();
@@ -148,17 +189,17 @@
 // Tests that FindInPage works when searching for white space.
 TEST_F(FindInPageJsTest, FindWhiteSpace) {
   ASSERT_TRUE(LoadHtml("<span> </span>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value(" "));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         double count = result->GetDouble();
@@ -175,17 +216,17 @@
 TEST_F(FindInPageJsTest, FindAcrossMultipleNodes) {
   ASSERT_TRUE(
       LoadHtml("<p>xx1<span>2</span>3<a>4512345xxx12</a>34<a>5xxx12345xx</p>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value(kFindString12345));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         double count = result->GetDouble();
@@ -202,18 +243,17 @@
 // accessibility string is returned.
 TEST_F(FindInPageJsTest, FindHighlightMatch) {
   ASSERT_TRUE(LoadHtml("<span>some foo match</span>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
-
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value(kFindStringFoo));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         double count = result->GetDouble();
@@ -229,8 +269,8 @@
   __block std::string context_string;
   std::vector<base::Value> highlight_params;
   highlight_params.push_back(base::Value(0));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSelectAndScrollToMatch, highlight_params,
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
       base::BindOnce(^(const base::Value* result) {
         highlight_done = true;
         context_string =
@@ -251,18 +291,17 @@
 // highlight is removed when another match is highlighted.
 TEST_F(FindInPageJsTest, FindHighlightSeparateMatches) {
   ASSERT_TRUE(LoadHtml("<span>foo foo match</span>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
-
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value(kFindStringFoo));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         double count = result->GetDouble();
@@ -278,8 +317,8 @@
   __block std::string context_string;
   std::vector<base::Value> highlight_params;
   highlight_params.push_back(base::Value(0));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSelectAndScrollToMatch, highlight_params,
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
       base::BindOnce(^(const base::Value* result) {
         highlight_done = true;
         context_string =
@@ -298,9 +337,9 @@
   highlight_done = false;
   std::vector<base::Value> highlight_second_params;
   highlight_second_params.push_back(base::Value(1));
-  main_web_frame()->CallJavaScriptFunction(
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
       kFindInPageSelectAndScrollToMatch, highlight_second_params,
-      base::BindOnce(^(const base::Value* result) {
+      content_world_, base::BindOnce(^(const base::Value* result) {
         highlight_done = true;
         context_string =
             result->FindKey(kSelectAndScrollResultContextString)->GetString();
@@ -324,18 +363,17 @@
 // Tests that FindInPage does not highlight any matches given an invalid index.
 TEST_F(FindInPageJsTest, FindHighlightMatchAtInvalidIndex) {
   ASSERT_TRUE(LoadHtml("<span>invalid </span>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
-
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value(kFindStringFoo));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         double count = result->GetDouble();
@@ -350,8 +388,8 @@
   __block bool highlight_done = false;
   std::vector<base::Value> highlight_params;
   highlight_params.push_back(base::Value(0));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSelectAndScrollToMatch, highlight_params,
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
       base::BindOnce(^(const base::Value* result) {
         highlight_done = true;
       }),
@@ -369,17 +407,17 @@
 // characters.
 TEST_F(FindInPageJsTest, SearchForNonAscii) {
   ASSERT_TRUE(LoadHtml("<span>école francais</span>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value("école"));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         double count = result->GetDouble();
@@ -402,18 +440,17 @@
   // on all devices.
   ASSERT_TRUE(
       LoadHtml("<div style=\"height: 4000px;\"></div><span>foo</span>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
-
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value("foo"));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         ASSERT_EQ(1.0, result->GetDouble());
@@ -427,8 +464,8 @@
   __block bool highlight_done = false;
   std::vector<base::Value> highlight_params;
   highlight_params.push_back(base::Value(0));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSelectAndScrollToMatch, highlight_params,
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
       base::BindOnce(^(const base::Value* result) {
         highlight_done = true;
       }),
@@ -452,19 +489,18 @@
 // Tests that FindInPage is able to clear CSS and match highlighting.
 TEST_F(FindInPageJsTest, StopFindInPage) {
   ASSERT_TRUE(LoadHtml("<span>foo foo</span>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
-
   // Do a search to ensure match highlighting is cleared properly.
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value("foo"));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         message_received = true;
       }),
       kCallJavascriptFunctionTimeout);
@@ -476,8 +512,8 @@
   message_received = false;
   std::vector<base::Value> highlight_params;
   highlight_params.push_back(base::Value(0));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSelectAndScrollToMatch, highlight_params,
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
       base::BindOnce(^(const base::Value* result) {
         message_received = true;
       }),
@@ -488,8 +524,8 @@
   }));
 
   message_received = false;
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageStop, std::vector<base::Value>(),
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageStop, std::vector<base::Value>(), content_world_,
       base::BindOnce(^(const base::Value* result) {
         message_received = true;
       }),
@@ -511,17 +547,17 @@
 TEST_F(FindInPageJsTest, HiddenMatch) {
   ASSERT_TRUE(
       LoadHtml("<span style='display:none'>foo</span><span>foo bar</span>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value(kFindStringFoo));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         double count = result->GetDouble();
@@ -536,8 +572,8 @@
   message_received = false;
   std::vector<base::Value> highlight_params;
   highlight_params.push_back(base::Value(0));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSelectAndScrollToMatch, highlight_params,
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
       base::BindOnce(^(const base::Value* result) {
         message_received = true;
       }),
@@ -562,17 +598,17 @@
 TEST_F(FindInPageJsTest, HiddenMatchBecomesVisible) {
   ASSERT_TRUE(LoadHtml("<span>foo</span><span id=\"hidden_match\" "
                        "style='display:none'>foo</span>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value(kFindStringFoo));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         double count = result->GetDouble();
@@ -589,8 +625,8 @@
   message_received = false;
   std::vector<base::Value> highlight_params;
   highlight_params.push_back(base::Value(0));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSelectAndScrollToMatch, highlight_params,
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSelectAndScrollToMatch, highlight_params, content_world_,
       base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_dict());
@@ -615,17 +651,17 @@
 TEST_F(FindInPageJsTest, MatchBecomesInvisible) {
   ASSERT_TRUE(LoadHtml(
       "<span>foo foo </span> <span id=\"matches_to_hide\">foo foo</span>"));
+  ASSERT_TRUE(WaitForWebFramesCount(1));
+
   const base::TimeDelta kCallJavascriptFunctionTimeout =
       base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
-  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
-    return all_web_frames().size() == 1;
-  }));
   __block bool message_received = false;
   std::vector<base::Value> params;
   params.push_back(base::Value(kFindStringFoo));
   params.push_back(base::Value(kPumpSearchTimeout));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSearch, params, base::BindOnce(^(const base::Value* result) {
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSearch, params, content_world_,
+      base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_double());
         double count = result->GetDouble();
@@ -640,8 +676,8 @@
   __block bool select_last_match_message_received = false;
   std::vector<base::Value> select_params;
   select_params.push_back(base::Value(3));
-  main_web_frame()->CallJavaScriptFunction(
-      kFindInPageSelectAndScrollToMatch, select_params,
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
+      kFindInPageSelectAndScrollToMatch, select_params, content_world_,
       base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_dict());
@@ -662,9 +698,9 @@
   __block bool select_third_match_message_received = false;
   std::vector<base::Value> select_third_match_params;
   select_third_match_params.push_back(base::Value(2));
-  main_web_frame()->CallJavaScriptFunction(
+  main_web_frame()->CallJavaScriptFunctionInContentWorld(
       kFindInPageSelectAndScrollToMatch, select_third_match_params,
-      base::BindOnce(^(const base::Value* result) {
+      content_world_, base::BindOnce(^(const base::Value* result) {
         ASSERT_TRUE(result);
         ASSERT_TRUE(result->is_dict());
         const base::Value* index = result->FindKey(kSelectAndScrollResultIndex);
diff --git a/ios/web/find_in_page/find_in_page_manager_impl.h b/ios/web/find_in_page/find_in_page_manager_impl.h
index 51b5ab7e..d9ddfede 100644
--- a/ios/web/find_in_page/find_in_page_manager_impl.h
+++ b/ios/web/find_in_page/find_in_page_manager_impl.h
@@ -55,7 +55,7 @@
   // null, then does nothing more.
   void ProcessFindInPageResult(const std::string& frame_id,
                                const int request_id,
-                               const base::Value* result);
+                               base::Optional<int> result);
   // Calls delegate DidHighlightMatches() method if |delegate_| is set and
   // starts a FindInPageNext find. Called when the last frame returns results
   // from a Find request.
@@ -75,6 +75,7 @@
                                      WebFrame* web_frame) override;
   void WebStateDestroyed(WebState* web_state) override;
 
+ protected:
   // Holds the state of the most recent find in page request.
   FindInPageRequest last_find_request_;
   FindInPageManagerDelegate* delegate_ = nullptr;
diff --git a/ios/web/find_in_page/find_in_page_manager_impl.mm b/ios/web/find_in_page/find_in_page_manager_impl.mm
index b4a1b4a1..994720fb 100644
--- a/ios/web/find_in_page/find_in_page_manager_impl.mm
+++ b/ios/web/find_in_page/find_in_page_manager_impl.mm
@@ -10,6 +10,7 @@
 #include "base/task/post_task.h"
 #include "base/values.h"
 #import "ios/web/find_in_page/find_in_page_constants.h"
+#import "ios/web/find_in_page/find_in_page_java_script_feature.h"
 #import "ios/web/public/find_in_page/find_in_page_manager_delegate.h"
 #import "ios/web/public/js_messaging/web_frame.h"
 #include "ios/web/public/js_messaging/web_frame_util.h"
@@ -21,20 +22,6 @@
 #error "This file requires ARC support."
 #endif
 
-namespace {
-// Timeout for the find within JavaScript in milliseconds.
-const double kFindInPageFindTimeout = 100.0;
-
-// Value returned when |kFindInPageSearch| call times out.
-const int kFindInPagePending = -1;
-
-// The timeout for JavaScript function calls in milliseconds. Important that
-// this is longer than |kFindInPageFindTimeout| to allow for incomplete find to
-// restart again. If this timeout hits, then something went wrong with the find
-// and find in page should not continue.
-const double kJavaScriptFunctionCallTimeout = 200.0;
-}  // namespace
-
 namespace web {
 
 // static
@@ -122,16 +109,13 @@
     return;
   }
 
-  std::vector<base::Value> params;
-  params.push_back(base::Value(base::SysNSStringToUTF8(query)));
-  params.push_back(base::Value(kFindInPageFindTimeout));
   for (WebFrame* frame : all_frames) {
-    bool result = frame->CallJavaScriptFunction(
-        kFindInPageSearch, params,
+    bool result = FindInPageJavaScriptFeature::GetInstance()->Search(
+        frame, base::SysNSStringToUTF8(query),
         base::BindOnce(&FindInPageManagerImpl::ProcessFindInPageResult,
                        weak_factory_.GetWeakPtr(), frame->GetFrameId(),
-                       last_find_request_.GetRequestId()),
-        base::TimeDelta::FromMilliseconds(kJavaScriptFunctionCallTimeout));
+                       last_find_request_.GetRequestId()));
+
     if (!result) {
       // Calling JavaScript function failed or the frame does not support
       // messaging.
@@ -151,9 +135,8 @@
   last_find_request_.Reset(/*new_query=*/nil,
                            /*new_pending_frame_call_count=*/0);
 
-  std::vector<base::Value> params;
   for (WebFrame* frame : web_state_->GetWebFramesManager()->GetAllWebFrames()) {
-    frame->CallJavaScriptFunction(kFindInPageStop, params);
+    FindInPageJavaScriptFeature::GetInstance()->Stop(frame);
   }
   if (delegate_) {
     delegate_->DidHighlightMatches(web_state_,
@@ -166,9 +149,10 @@
   return web_state_->ContentIsHTML();
 }
 
-void FindInPageManagerImpl::ProcessFindInPageResult(const std::string& frame_id,
-                                                    const int unique_id,
-                                                    const base::Value* result) {
+void FindInPageManagerImpl::ProcessFindInPageResult(
+    const std::string& frame_id,
+    const int unique_id,
+    base::Optional<int> result_matches) {
   if (unique_id != last_find_request_.GetRequestId()) {
     // New find was started or current find was stopped.
     return;
@@ -179,32 +163,23 @@
   }
 
   WebFrame* frame = GetWebFrameWithId(web_state_, frame_id);
-  if (!result || !frame) {
+  if (!result_matches || !frame) {
     // The frame no longer exists or the function call timed out. In both cases,
     // result will be null.
     // Zero out count to ensure every frame is updated for every find.
     last_find_request_.SetMatchCountForFrame(0, frame_id);
   } else {
-    int match_count = 0;
-    if (result->is_double()) {
-      // Valid match number returned. If not, match count will be 0 in order to
-      // zero-out count from previous find.
-      match_count = static_cast<int>(result->GetDouble());
-    }
     // If response is equal to kFindInPagePending, find did not finish in the
     // JavaScript. Call pumpSearch to continue find.
-    if (match_count == kFindInPagePending) {
-      std::vector<base::Value> params;
-      params.push_back(base::Value(kFindInPageFindTimeout));
-      frame->CallJavaScriptFunction(
-          kFindInPagePump, params,
+    if (result_matches.value() == find_in_page::kFindInPagePending) {
+      FindInPageJavaScriptFeature::GetInstance()->Pump(
+          frame,
           base::BindOnce(&FindInPageManagerImpl::ProcessFindInPageResult,
-                         weak_factory_.GetWeakPtr(), frame_id, unique_id),
-          base::TimeDelta::FromMilliseconds(kJavaScriptFunctionCallTimeout));
+                         weak_factory_.GetWeakPtr(), frame_id, unique_id));
       return;
     }
 
-    last_find_request_.SetMatchCountForFrame(match_count, frame_id);
+    last_find_request_.SetMatchCountForFrame(result_matches.value(), frame_id);
   }
   last_find_request_.DidReceiveFindResponseFromOneFrame();
   if (last_find_request_.AreAllFindResponsesReturned()) {
@@ -283,14 +258,10 @@
   web::WebFrame* frame =
       GetWebFrameWithId(web_state_, last_find_request_.GetSelectedFrameId());
   if (frame) {
-    std::vector<base::Value> params;
-    params.push_back(
-        base::Value(last_find_request_.GetCurrentSelectedMatchFrameIndex()));
-    frame->CallJavaScriptFunction(
-        kFindInPageSelectAndScrollToMatch, params,
+    FindInPageJavaScriptFeature::GetInstance()->SelectMatch(
+        frame, last_find_request_.GetCurrentSelectedMatchFrameIndex(),
         base::BindOnce(&FindInPageManagerImpl::SelectDidFinish,
-                       weak_factory_.GetWeakPtr()),
-        base::TimeDelta::FromMilliseconds(kJavaScriptFunctionCallTimeout));
+                       weak_factory_.GetWeakPtr()));
   }
 }
 
diff --git a/ios/web/find_in_page/find_in_page_manager_inttest.mm b/ios/web/find_in_page/find_in_page_manager_inttest.mm
index 64005e2b..ed11604 100644
--- a/ios/web/find_in_page/find_in_page_manager_inttest.mm
+++ b/ios/web/find_in_page/find_in_page_manager_inttest.mm
@@ -4,9 +4,11 @@
 
 #import "base/test/ios/wait_util.h"
 #include "ios/testing/embedded_test_server_handlers.h"
+#import "ios/web/find_in_page/find_in_page_java_script_feature.h"
 #import "ios/web/public/find_in_page/find_in_page_manager.h"
 #import "ios/web/public/js_messaging/web_frames_manager.h"
 #import "ios/web/public/test/fakes/fake_find_in_page_manager_delegate.h"
+#import "ios/web/public/test/fakes/fake_web_client.h"
 #import "ios/web/public/test/navigation_test_util.h"
 #import "ios/web/public/test/web_test_with_web_state.h"
 #include "net/base/escape.h"
@@ -36,8 +38,16 @@
 // FindInPageManagerDelegate are correct.
 class FindInPageManagerTest : public WebTestWithWebState {
  protected:
+  FindInPageManagerTest()
+      : WebTestWithWebState(std::make_unique<FakeWebClient>()) {
+    static_cast<web::FakeWebClient*>(WebTestWithWebState::GetWebClient())
+        ->SetJavaScriptFeatures({FindInPageJavaScriptFeature::GetInstance()});
+  }
+
   void SetUp() override {
     WebTestWithWebState::SetUp();
+    ConfigureJavaScriptFeatures();
+
     test_server_.RegisterRequestHandler(base::BindRepeating(
         &net::test_server::HandlePrefixedRequest, "/echo-query",
         base::BindRepeating(&testing::HandlePageWithContents)));
diff --git a/ios/web/find_in_page/find_in_page_manger_impl_unittest.mm b/ios/web/find_in_page/find_in_page_manger_impl_unittest.mm
index 55aacfd..f9fb1be 100644
--- a/ios/web/find_in_page/find_in_page_manger_impl_unittest.mm
+++ b/ios/web/find_in_page/find_in_page_manger_impl_unittest.mm
@@ -9,12 +9,15 @@
 #include "base/test/metrics/user_action_tester.h"
 #include "base/values.h"
 #import "ios/web/find_in_page/find_in_page_constants.h"
+#import "ios/web/find_in_page/find_in_page_java_script_feature.h"
+#import "ios/web/js_messaging/java_script_feature_manager.h"
 #import "ios/web/public/js_messaging/web_frames_manager.h"
 #import "ios/web/public/test/fakes/fake_find_in_page_manager_delegate.h"
-#include "ios/web/public/test/fakes/fake_web_frame.h"
+#import "ios/web/public/test/fakes/fake_web_client.h"
 #import "ios/web/public/test/fakes/fake_web_frames_manager.h"
 #import "ios/web/public/test/fakes/fake_web_state.h"
 #include "ios/web/public/test/web_test.h"
+#include "ios/web/test/fakes/fake_web_frame_internal.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -30,11 +33,19 @@
 // FindInPageManagerDelegate is correct depending on what web frames return.
 class FindInPageManagerImplTest : public WebTest {
  protected:
-  FindInPageManagerImplTest() {
+  FindInPageManagerImplTest() : WebTest(std::make_unique<FakeWebClient>()) {
     fake_web_state_ = std::make_unique<FakeWebState>();
+    fake_web_state_->SetBrowserState(GetBrowserState());
     auto frames_manager = std::make_unique<FakeWebFramesManager>();
     fake_web_frames_manager_ = frames_manager.get();
     fake_web_state_->SetWebFramesManager(std::move(frames_manager));
+  }
+
+  void SetUp() override {
+    WebTest::SetUp();
+
+    JavaScriptFeatureManager::FromBrowserState(GetBrowserState())
+        ->ConfigureFeatures({FindInPageJavaScriptFeature::GetInstance()});
     FindInPageManagerImpl::CreateForWebState(fake_web_state_.get());
     GetFindInPageManager()->SetDelegate(&fake_delegate_);
   }
@@ -43,26 +54,28 @@
   FindInPageManager* GetFindInPageManager() {
     return FindInPageManager::FromWebState(fake_web_state_.get());
   }
-  // Returns a FakeWebFrame that is the main frame with id |frame_id| that will
-  // return |js_result| for the JavaScript function call
-  // "findInString.findString".
-  std::unique_ptr<FakeWebFrame> CreateMainWebFrameWithJsResultForFind(
+
+  // Returns a fake WebFrame that represents the main frame which will return
+  // |js_result| for the JavaScript function call "findInString.findString".
+  std::unique_ptr<FakeWebFrameInternal> CreateMainWebFrameWithJsResultForFind(
       std::unique_ptr<base::Value> js_result) {
-    auto frame = std::make_unique<FakeMainWebFrame>(GURL());
+    auto frame = std::make_unique<FakeMainWebFrameInternal>(GURL());
     frame->AddJsResultForFunctionCall(std::move(js_result), kFindInPageSearch);
-    return frame;
-  }
-  // Returns a FakeWebFrame child frame with id |frame_id| that
-  // will return |js_result| for the JavaScript function call
-  // "findInString.findString".
-  std::unique_ptr<FakeWebFrame> CreateChildWebFrameWithJsResultForFind(
-      std::unique_ptr<base::Value> js_result) {
-    auto frame = std::make_unique<FakeChildWebFrame>(GURL());
-    frame->AddJsResultForFunctionCall(std::move(js_result), kFindInPageSearch);
+    frame->set_browser_state(GetBrowserState());
     return frame;
   }
 
-  void AddWebFrame(std::unique_ptr<WebFrame> frame) {
+  // Returns a fake WebFrame that represents a child frame which will return
+  // |js_result| for the JavaScript function call "findInString.findString".
+  std::unique_ptr<FakeWebFrameInternal> CreateChildWebFrameWithJsResultForFind(
+      std::unique_ptr<base::Value> js_result) {
+    auto frame = std::make_unique<FakeChildWebFrameInternal>(GURL());
+    frame->AddJsResultForFunctionCall(std::move(js_result), kFindInPageSearch);
+    frame->set_browser_state(GetBrowserState());
+    return frame;
+  }
+
+  void AddWebFrame(std::unique_ptr<FakeWebFrame> frame) {
     WebFrame* frame_ptr = frame.get();
     fake_web_frames_manager_->AddWebFrame(std::move(frame));
     fake_web_state_->OnWebFrameDidBecomeAvailable(frame_ptr);
diff --git a/ios/web/web_state/js/resources/find_in_page.js b/ios/web/find_in_page/resources/find_in_page.js
similarity index 98%
rename from ios/web/web_state/js/resources/find_in_page.js
rename to ios/web/find_in_page/resources/find_in_page.js
index f3ae1e3..5c5fcb9f 100644
--- a/ios/web/web_state/js/resources/find_in_page.js
+++ b/ios/web/find_in_page/resources/find_in_page.js
@@ -21,8 +21,8 @@
  */
 __gCrWeb.findInPage = {};
 
- // Store common namespace object in a global __gCrWeb object referenced by a
- // string, so it does not get renamed by closure compiler during the
+ // Store findInPage namespace object in a global __gCrWeb object referenced by
+ // a string, so it does not get renamed by closure compiler during the
  // minification.
 __gCrWeb['findInPage'] = __gCrWeb.findInPage;
 
@@ -913,6 +913,4 @@
   return text.replace(REGEX_ESCAPER, '\\$1');
 };
 
-window.addEventListener('pagehide', __gCrWeb.findInPage.stop);
-
 })();
diff --git a/ios/web/find_in_page/resources/find_in_page_event_listeners.js b/ios/web/find_in_page/resources/find_in_page_event_listeners.js
new file mode 100644
index 0000000..4c57da7e
--- /dev/null
+++ b/ios/web/find_in_page/resources/find_in_page_event_listeners.js
@@ -0,0 +1,8 @@
+// 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.
+
+// Requires __crWeb.findInPage
+goog.provide('__crWeb.findInPageEventListeners');
+
+window.addEventListener('pagehide', __gCrWeb.findInPage.stop);
diff --git a/ios/web/js_messaging/BUILD.gn b/ios/web/js_messaging/BUILD.gn
index 24876ce..b0032bd 100644
--- a/ios/web/js_messaging/BUILD.gn
+++ b/ios/web/js_messaging/BUILD.gn
@@ -91,6 +91,7 @@
 
   deps = [
     "//base",
+    "//ios/web/find_in_page",
     "//ios/web/js_features/context_menu",
     "//ios/web/js_features/scroll_helper",
     "//ios/web/js_features/window_error",
diff --git a/ios/web/js_messaging/java_script_feature_util_impl.mm b/ios/web/js_messaging/java_script_feature_util_impl.mm
index cf723f3..aeb0cfe9 100644
--- a/ios/web/js_messaging/java_script_feature_util_impl.mm
+++ b/ios/web/js_messaging/java_script_feature_util_impl.mm
@@ -8,6 +8,7 @@
 
 #include "base/logging.h"
 #import "base/strings/sys_string_conversions.h"
+#import "ios/web/find_in_page/find_in_page_java_script_feature.h"
 #include "ios/web/js_features/context_menu/context_menu_java_script_feature.h"
 #include "ios/web/js_features/scroll_helper/scroll_helper_java_script_feature.h"
 #import "ios/web/js_features/window_error/window_error_java_script_feature.h"
@@ -100,6 +101,7 @@
 std::vector<JavaScriptFeature*> GetBuiltInJavaScriptFeatures(
     BrowserState* browser_state) {
   return {ContextMenuJavaScriptFeature::FromBrowserState(browser_state),
+          FindInPageJavaScriptFeature::GetInstance(),
           GetPluginPlaceholderJavaScriptFeature(),
           GetScrollHelperJavaScriptFeature(),
           GetWindowErrorJavaScriptFeature()};
diff --git a/ios/web/public/test/BUILD.gn b/ios/web/public/test/BUILD.gn
index 987a99fe..4903b0f 100644
--- a/ios/web/public/test/BUILD.gn
+++ b/ios/web/public/test/BUILD.gn
@@ -18,6 +18,7 @@
     "//ios/web:web",
     "//ios/web/common:features",
     "//ios/web/js_messaging",
+    "//ios/web/js_messaging:java_script_feature",
     "//ios/web/navigation",
     "//ios/web/navigation:wk_navigation_util",
     "//ios/web/public/deprecated",
@@ -64,6 +65,7 @@
     "//base",
     "//base/test:test_support",
     "//ios/net",
+    "//ios/web/find_in_page",
     "//ios/web/js_messaging",
     "//ios/web/public:public",
     "//ios/web/public/js_messaging",
diff --git a/ios/web/public/test/web_test.h b/ios/web/public/test/web_test.h
index 23b2070..51d49dab 100644
--- a/ios/web/public/test/web_test.h
+++ b/ios/web/public/test/web_test.h
@@ -28,6 +28,13 @@
           WebTaskEnvironment::Options = WebTaskEnvironment::Options::DEFAULT);
   ~WebTest() override;
 
+  // Manually configures JavaScriptFeatures from |GetWebClient()|. This needs to
+  // called from |SetUp| when features are manually set on a FakeWebClient.
+  // NOTE: Do not call this when using a ChromeWebClient as this will override
+  // built in features features with only the features returned by the web
+  // client.
+  void ConfigureJavaScriptFeatures();
+
   // Returns the WebClient that is used for testing.
   virtual web::WebClient* GetWebClient();
 
diff --git a/ios/web/public/test/web_test.mm b/ios/web/public/test/web_test.mm
index 6d04293..553274d0 100644
--- a/ios/web/public/test/web_test.mm
+++ b/ios/web/public/test/web_test.mm
@@ -5,6 +5,7 @@
 #include "ios/web/public/test/web_test.h"
 
 #include "base/memory/ptr_util.h"
+#import "ios/web/js_messaging/java_script_feature_manager.h"
 #include "ios/web/public/deprecated/global_web_state_observer.h"
 #import "ios/web/public/test/fakes/fake_web_client.h"
 
@@ -35,6 +36,12 @@
 
 WebTest::~WebTest() {}
 
+void WebTest::ConfigureJavaScriptFeatures() {
+  JavaScriptFeatureManager::FromBrowserState(GetBrowserState())
+      ->ConfigureFeatures(
+          GetWebClient()->GetJavaScriptFeatures(GetBrowserState()));
+}
+
 web::WebClient* WebTest::GetWebClient() {
   return web_client_.Get();
 }
diff --git a/ios/web/public/test/web_view_content_test_util.h b/ios/web/public/test/web_view_content_test_util.h
index 1353146..5ee109f 100644
--- a/ios/web/public/test/web_view_content_test_util.h
+++ b/ios/web/public/test/web_view_content_test_util.h
@@ -25,8 +25,10 @@
 // Otherwise, returns false.
 bool IsWebViewContainingText(web::WebState* web_state, const std::string& text);
 
-// Returns true if there is a a frame from |web_state| that contains |text|.
-// This method is waiting for the duration of the JavaScript messages exchange.
+// Returns true if there is a frame from |web_state| that contains |text|.
+// This method waits for the JavaScript message response.
+// |FindInPageJavaScriptFeature| must be configured for |web_state| in order for
+// this function to correctly return results.
 bool IsWebViewContainingTextInFrame(web::WebState* web_state,
                                     const std::string& text);
 
@@ -48,6 +50,8 @@
 
 // Waits for the given web state to have a frame that contains |text|. If the
 // condition is not met within |timeout| false is returned.
+// |FindInPageJavaScriptFeature| must be configured for |web_state| in order for
+// this function to correctly return results.
 bool WaitForWebViewContainingTextInFrame(
     web::WebState* web_state,
     std::string text,
diff --git a/ios/web/public/test/web_view_content_test_util.mm b/ios/web/public/test/web_view_content_test_util.mm
index ef26fb3..2ef1d93 100644
--- a/ios/web/public/test/web_view_content_test_util.mm
+++ b/ios/web/public/test/web_view_content_test_util.mm
@@ -12,7 +12,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/values.h"
-#import "ios/web/public/js_messaging/web_frame.h"
+#import "ios/web/find_in_page/find_in_page_java_script_feature.h"
 #import "ios/web/public/js_messaging/web_frames_manager.h"
 #import "ios/web/public/test/web_view_interaction_test_util.h"
 #import "ios/web/public/web_state.h"
@@ -102,25 +102,20 @@
 
 bool IsWebViewContainingTextInFrame(web::WebState* web_state,
                                     const std::string& text) {
-  WebFramesManager* frames_manager = web_state->GetWebFramesManager();
-  const base::TimeDelta kCallJavascriptFunctionTimeout =
-      base::TimeDelta::FromSeconds(kWaitForJSCompletionTimeout);
   __block NSInteger number_frames_processing = 0;
   __block bool text_found = false;
-  for (WebFrame* frame : frames_manager->GetAllWebFrames()) {
+  for (WebFrame* frame : web_state->GetWebFramesManager()->GetAllWebFrames()) {
     number_frames_processing++;
-    std::vector<base::Value> parameters;
-    parameters.push_back(base::Value(text));
-    parameters.push_back(base::Value(100.0));
-    frame->CallJavaScriptFunction("findInPage.findString", parameters,
-                                  base::BindOnce(^(const base::Value* value) {
-                                    if (value) {
-                                      text_found =
-                                          text_found || value->GetDouble() != 0;
-                                    }
-                                    number_frames_processing--;
-                                  }),
-                                  kCallJavascriptFunctionTimeout);
+
+    FindInPageJavaScriptFeature* find_in_page_feature =
+        FindInPageJavaScriptFeature::GetInstance();
+    find_in_page_feature->Search(
+        frame, text, base::BindOnce(^(base::Optional<int> result_matches) {
+          if (result_matches && result_matches.value() >= 1) {
+            text_found = true;
+          }
+          number_frames_processing--;
+        }));
   }
   bool success = WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
     if (text_found)
diff --git a/ios/web/test/fakes/fake_web_frame_internal.cc b/ios/web/test/fakes/fake_web_frame_internal.cc
index 5b2bcd41..ef271a34 100644
--- a/ios/web/test/fakes/fake_web_frame_internal.cc
+++ b/ios/web/test/fakes/fake_web_frame_internal.cc
@@ -6,6 +6,41 @@
 
 namespace web {
 
+std::string FakeWebFrameInternal::GetFrameId() const {
+  return FakeWebFrame::GetFrameId();
+}
+
+bool FakeWebFrameInternal::IsMainFrame() const {
+  return FakeWebFrame::IsMainFrame();
+}
+
+GURL FakeWebFrameInternal::GetSecurityOrigin() const {
+  return FakeWebFrame::GetSecurityOrigin();
+}
+
+bool FakeWebFrameInternal::CanCallJavaScriptFunction() const {
+  return FakeWebFrame::CanCallJavaScriptFunction();
+}
+
+BrowserState* FakeWebFrameInternal::GetBrowserState() {
+  return FakeWebFrame::GetBrowserState();
+}
+
+bool FakeWebFrameInternal::CallJavaScriptFunction(
+    const std::string& name,
+    const std::vector<base::Value>& parameters) {
+  return FakeWebFrame::CallJavaScriptFunction(name, parameters);
+}
+
+bool FakeWebFrameInternal::CallJavaScriptFunction(
+    const std::string& name,
+    const std::vector<base::Value>& parameters,
+    base::OnceCallback<void(const base::Value*)> callback,
+    base::TimeDelta timeout) {
+  return FakeWebFrame::CallJavaScriptFunction(name, parameters,
+                                              std::move(callback), timeout);
+}
+
 bool FakeWebFrameInternal::CallJavaScriptFunctionInContentWorld(
     const std::string& name,
     const std::vector<base::Value>& parameters,
diff --git a/ios/web/test/fakes/fake_web_frame_internal.h b/ios/web/test/fakes/fake_web_frame_internal.h
index 3cdebdf..a535629 100644
--- a/ios/web/test/fakes/fake_web_frame_internal.h
+++ b/ios/web/test/fakes/fake_web_frame_internal.h
@@ -6,7 +6,7 @@
 #define IOS_WEB_TEST_FAKES_FAKE_WEB_FRAME_INTERNAL_H_
 
 #include "ios/web/js_messaging/web_frame_internal.h"
-#import "ios/web/public/test/fakes/fake_web_frame.h"
+#include "ios/web/public/test/fakes/fake_web_frame.h"
 
 namespace web {
 
@@ -22,6 +22,22 @@
   // call to |CallJavaScriptFunctionInContentWorld|.
   JavaScriptContentWorld* last_received_content_world();
 
+  // WebFrame:
+  // NOTE: These WebFrame overrides simply call the FakeWebFrame implementation.
+  std::string GetFrameId() const override;
+  bool IsMainFrame() const override;
+  GURL GetSecurityOrigin() const override;
+  bool CanCallJavaScriptFunction() const override;
+  BrowserState* GetBrowserState() override;
+  bool CallJavaScriptFunction(
+      const std::string& name,
+      const std::vector<base::Value>& parameters) override;
+  bool CallJavaScriptFunction(
+      const std::string& name,
+      const std::vector<base::Value>& parameters,
+      base::OnceCallback<void(const base::Value*)> callback,
+      base::TimeDelta timeout) override;
+
   // WebFrameInternal:
   // If |CanCallJavaScriptFunction()| is true, the JavaScript call which would
   // be executed by a real WebFrame will be added to |java_script_calls_|.
diff --git a/ios/web/web_state/js/resources/all_frames_web_bundle.js b/ios/web/web_state/js/resources/all_frames_web_bundle.js
index 25425a28..385ceb15 100644
--- a/ios/web/web_state/js/resources/all_frames_web_bundle.js
+++ b/ios/web/web_state/js/resources/all_frames_web_bundle.js
@@ -7,5 +7,4 @@
 
 // Requires __crWeb.base, __crWeb.common, and __crWeb.message
 goog.require('__crWeb.cookie');
-goog.require('__crWeb.findInPage');
 goog.require('__crWeb.shareWorkaround');
diff --git a/ios/web/web_state/web_state_observer_inttest.mm b/ios/web/web_state/web_state_observer_inttest.mm
index b4c873a..db2a4722 100644
--- a/ios/web/web_state/web_state_observer_inttest.mm
+++ b/ios/web/web_state/web_state_observer_inttest.mm
@@ -21,6 +21,8 @@
 #import "ios/net/protocol_handler_util.h"
 #include "ios/testing/embedded_test_server_handlers.h"
 #include "ios/web/common/features.h"
+#import "ios/web/find_in_page/find_in_page_java_script_feature.h"
+#import "ios/web/js_messaging/java_script_feature_manager.h"
 #include "ios/web/navigation/wk_navigation_util.h"
 #import "ios/web/public/navigation/navigation_context.h"
 #import "ios/web/public/navigation/navigation_item.h"
@@ -30,6 +32,7 @@
 #import "ios/web/public/session/crw_session_storage.h"
 #import "ios/web/public/test/error_test_util.h"
 #import "ios/web/public/test/fakes/async_web_state_policy_decider.h"
+#import "ios/web/public/test/fakes/fake_web_client.h"
 #include "ios/web/public/test/fakes/fake_web_state_observer.h"
 #import "ios/web/public/test/navigation_test_util.h"
 #import "ios/web/public/test/web_view_content_test_util.h"
@@ -781,6 +784,10 @@
 
   void SetUp() override {
     WebIntTest::SetUp();
+
+    JavaScriptFeatureManager::FromBrowserState(GetBrowserState())
+        ->ConfigureFeatures({FindInPageJavaScriptFeature::GetInstance()});
+
     decider_ = std::make_unique<StrictMock<PolicyDeciderMock>>(web_state());
     scoped_observer_.Add(web_state());
 
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 49144669..670d8b99 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -464,12 +464,12 @@
 const base::Feature kUnifiedAutoplay{"UnifiedAutoplay",
                                      base::FEATURE_ENABLED_BY_DEFAULT};
 
-#if defined(OS_LINUX) || defined(OS_FREEBSD)
+#if defined(OS_LINUX)
 // Enable vaapi video decoding on linux. This is already enabled by default on
 // chromeos, but needs an experiment on linux.
 const base::Feature kVaapiVideoDecodeLinux{"VaapiVideoDecoder",
                                            base::FEATURE_DISABLED_BY_DEFAULT};
-#endif  // defined(OS_LINUX) || defined(OS_FREEBSD)
+#endif  // defined(OS_LINUX)
 
 // Enable VA-API hardware decode acceleration for AV1.
 const base::Feature kVaapiAV1Decoder{"VaapiAV1Decoder",
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 08db9fca..cea1ae9 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -183,9 +183,9 @@
 MEDIA_EXPORT extern const base::Feature kUseMediaHistoryStore;
 MEDIA_EXPORT extern const base::Feature kUseR16Texture;
 MEDIA_EXPORT extern const base::Feature kUseSodaForLiveCaption;
-#if defined(OS_LINUX) || defined(OS_FREEBSD)
+#if defined(OS_LINUX)
 MEDIA_EXPORT extern const base::Feature kVaapiVideoDecodeLinux;
-#endif  // defined(OS_LINUX) || defined(OS_FREEBSD)
+#endif  // defined(OS_LINUX)
 MEDIA_EXPORT extern const base::Feature kVaapiAV1Decoder;
 MEDIA_EXPORT extern const base::Feature kVaapiLowPowerEncoderGen9x;
 MEDIA_EXPORT extern const base::Feature kVaapiEnforceVideoMinMaxResolution;
diff --git a/media/capture/video/file_video_capture_device.h b/media/capture/video/file_video_capture_device.h
index 7b37900..796202c 100644
--- a/media/capture/video/file_video_capture_device.h
+++ b/media/capture/video/file_video_capture_device.h
@@ -30,7 +30,7 @@
 // information on the file format. Several restrictions and notes apply, see the
 // implementation file.
 // Example Y4M videos can be found in http://media.xiph.org/video/derf.
-// Example MJPEG videos can be found in media/data/test/bear.mjpeg.
+// Example MJPEG videos can be found in media/test/data/bear.mjpeg.
 // Restrictions: Y4M videos should have .y4m file extension and MJPEG videos
 // should have .mjpeg file extension.
 class CAPTURE_EXPORT FileVideoCaptureDevice : public VideoCaptureDevice {
diff --git a/media/gpu/v4l2/v4l2_jpeg_encode_accelerator.cc b/media/gpu/v4l2/v4l2_jpeg_encode_accelerator.cc
index 0998c69..c011253 100644
--- a/media/gpu/v4l2/v4l2_jpeg_encode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_jpeg_encode_accelerator.cc
@@ -10,6 +10,7 @@
 #include <sys/mman.h>
 
 #include <memory>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
@@ -1912,9 +1913,8 @@
       video_frame, quality, exif_buffer, std::move(output_buffer)));
 
   encoder_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&V4L2JpegEncodeAccelerator::EncodeTaskLegacy,
-                     base::Unretained(this), base::Passed(&job_record)));
+      FROM_HERE, base::BindOnce(&V4L2JpegEncodeAccelerator::EncodeTaskLegacy,
+                                base::Unretained(this), std::move(job_record)));
 }
 
 void V4L2JpegEncodeAccelerator::EncodeWithDmaBuf(
@@ -1949,9 +1949,8 @@
       new JobRecord(input_frame, output_frame, quality, task_id, exif_buffer));
 
   encoder_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&V4L2JpegEncodeAccelerator::EncodeTask,
-                     base::Unretained(this), base::Passed(&job_record)));
+      FROM_HERE, base::BindOnce(&V4L2JpegEncodeAccelerator::EncodeTask,
+                                base::Unretained(this), std::move(job_record)));
 }
 
 void V4L2JpegEncodeAccelerator::EncodeTaskLegacy(
diff --git a/media/mojo/services/mojo_video_decoder_service.cc b/media/mojo/services/mojo_video_decoder_service.cc
index 450d27e..44ff3898 100644
--- a/media/mojo/services/mojo_video_decoder_service.cc
+++ b/media/mojo/services/mojo_video_decoder_service.cc
@@ -10,9 +10,12 @@
 #include "base/callback_helpers.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/optional.h"
+#include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "base/timer/elapsed_timer.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/simple_sync_token_client.h"
 #include "media/base/video_decoder.h"
@@ -43,6 +46,18 @@
 const char kDecodeTraceName[] = "MojoVideoDecoderService::Decode";
 const char kResetTraceName[] = "MojoVideoDecoderService::Reset";
 
+void RecordTimingHistogram(VideoDecoderImplementation impl,
+                           const char* method,
+                           base::TimeDelta elapsed) {
+  base::UmaHistogramTimes(
+      base::StringPrintf("Media.MojoVideoDecoderServiceTiming.%s.%s",
+                         impl == VideoDecoderImplementation::kDefault
+                             ? "Default"
+                             : "Alternate",
+                         method),
+      elapsed);
+}
+
 }  // namespace
 
 class VideoFrameHandleReleaserImpl final
@@ -99,6 +114,7 @@
 
 MojoVideoDecoderService::~MojoVideoDecoderService() {
   DVLOG(1) << __func__;
+  base::ElapsedTimer elapsed;
 
   if (init_cb_) {
     OnDecoderInitialized(
@@ -111,6 +127,14 @@
 
   if (is_active_instance_)
     g_num_active_mvd_instances--;
+
+  // Destruct the VideoDecoder here so its destruction duration is included by
+  // the histogram timer below.
+  weak_factory_.InvalidateWeakPtrs();
+  decoder_.reset();
+
+  if (implementation_)
+    RecordTimingHistogram(*implementation_, "Destruct", elapsed.Elapsed());
 }
 
 void MojoVideoDecoderService::GetSupportedConfigs(
@@ -139,6 +163,9 @@
     return;
   }
 
+  base::ElapsedTimer elapsed;
+  implementation_ = implementation;
+
   client_.Bind(std::move(client));
 
   scoped_refptr<base::SingleThreadTaskRunner> task_runner =
@@ -160,6 +187,8 @@
       base::BindRepeating(
           &MojoVideoDecoderService::OnDecoderRequestedOverlayInfo, weak_this_),
       target_color_space);
+
+  RecordTimingHistogram(*implementation_, "Construct", elapsed.Elapsed());
 }
 
 void MojoVideoDecoderService::Initialize(
diff --git a/media/mojo/services/mojo_video_decoder_service.h b/media/mojo/services/mojo_video_decoder_service.h
index da0ff47..d04d177d 100644
--- a/media/mojo/services/mojo_video_decoder_service.h
+++ b/media/mojo/services/mojo_video_decoder_service.h
@@ -86,6 +86,9 @@
       bool restart_for_transitions,
       ProvideOverlayInfoCB provide_overlay_info_cb);
 
+  // Implementation value provided at the time of Construct().
+  base::Optional<VideoDecoderImplementation> implementation_;
+
   // Whether this instance is active (Decode() was called at least once).
   bool is_active_instance_ = false;
 
diff --git a/mojo/public/cpp/platform/named_platform_channel_posix.cc b/mojo/public/cpp/platform/named_platform_channel_posix.cc
index 9082ac4d..cbedae6c 100644
--- a/mojo/public/cpp/platform/named_platform_channel_posix.cc
+++ b/mojo/public/cpp/platform/named_platform_channel_posix.cc
@@ -5,6 +5,7 @@
 #include "mojo/public/cpp/platform/named_platform_channel.h"
 
 #include <errno.h>
+#include <string.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <unistd.h>
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index d9430bc..8939aee 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -543,6 +543,11 @@
   return DoLoop(OK);
 }
 
+void HttpNetworkTransaction::ResumeAfterConnected(int result) {
+  DCHECK_EQ(next_state_, STATE_CONNECTED_CALLBACK_COMPLETE);
+  OnIOComplete(result);
+}
+
 void HttpNetworkTransaction::CloseConnectionOnDestruction() {
   close_connection_on_destruction_ = true;
 }
@@ -726,6 +731,9 @@
       case STATE_INIT_STREAM_COMPLETE:
         rv = DoInitStreamComplete(rv);
         break;
+      case STATE_CONNECTED_CALLBACK_COMPLETE:
+        rv = DoConnectedCallbackComplete(rv);
+        break;
       case STATE_GENERATE_PROXY_AUTH_TOKEN:
         DCHECK_EQ(OK, rv);
         rv = DoGenerateProxyAuthToken();
@@ -883,16 +891,24 @@
     return result;
   }
 
+  next_state_ = STATE_CONNECTED_CALLBACK_COMPLETE;
+
   // Fire off notification that we have successfully connected.
   if (!connected_callback_.is_null()) {
     TransportType type = TransportType::kDirect;
     if (!proxy_info_.is_direct()) {
       type = TransportType::kProxied;
     }
-    result = connected_callback_.Run(TransportInfo(type, remote_endpoint_));
-    DCHECK_NE(result, ERR_IO_PENDING);
+    result = connected_callback_.Run(
+        TransportInfo(type, remote_endpoint_),
+        base::BindOnce(&HttpNetworkTransaction::ResumeAfterConnected,
+                       base::Unretained(this)));
   }
 
+  return result;
+}
+
+int HttpNetworkTransaction::DoConnectedCallbackComplete(int result) {
   if (result == OK) {
     // Only transition if we succeeded. Otherwise stop at STATE_NONE.
     next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN;
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
index f95473ea..bf7c586a0 100644
--- a/net/http/http_network_transaction.h
+++ b/net/http/http_network_transaction.h
@@ -140,6 +140,7 @@
     STATE_CREATE_STREAM_COMPLETE,
     STATE_INIT_STREAM,
     STATE_INIT_STREAM_COMPLETE,
+    STATE_CONNECTED_CALLBACK_COMPLETE,
     STATE_GENERATE_PROXY_AUTH_TOKEN,
     STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE,
     STATE_GENERATE_SERVER_AUTH_TOKEN,
@@ -180,6 +181,7 @@
   int DoCreateStreamComplete(int result);
   int DoInitStream();
   int DoInitStreamComplete(int result);
+  int DoConnectedCallbackComplete(int results);
   int DoGenerateProxyAuthToken();
   int DoGenerateProxyAuthTokenComplete(int result);
   int DoGenerateServerAuthToken();
@@ -301,6 +303,8 @@
   // "Accept-Encoding".
   bool ContentEncodingsValid() const;
 
+  void ResumeAfterConnected(int result);
+
   scoped_refptr<HttpAuthController>
       auth_controllers_[HttpAuth::AUTH_NUM_TARGETS];
 
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index 29a5c811..f9eb6690 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -1053,6 +1053,57 @@
                           EmbeddedHttpServerTransportInfo()));
 }
 
+TEST_F(HttpNetworkTransactionTest, ConnectedCallbackCalledAsync) {
+  MockRead data_reads[] = {
+      MockRead("HTTP/1.0 200 OK\r\n"),
+      MockRead(SYNCHRONOUS, OK),
+  };
+  StaticSocketDataProvider data(data_reads, base::span<MockWrite>());
+  session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+  ConnectedHandler connected_handler;
+  connected_handler.set_run_callback(true);
+  std::unique_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
+  auto request = DefaultRequestInfo();
+  HttpNetworkTransaction transaction(DEFAULT_PRIORITY, session.get());
+  transaction.SetConnectedCallback(connected_handler.Callback());
+
+  TestCompletionCallback callback;
+  EXPECT_THAT(
+      transaction.Start(&request, callback.callback(), NetLogWithSource()),
+      IsError(ERR_IO_PENDING));
+  EXPECT_THAT(callback.WaitForResult(), IsOk());
+
+  EXPECT_THAT(connected_handler.transports(),
+              ElementsAre(EmbeddedHttpServerTransportInfo()));
+}
+
+TEST_F(HttpNetworkTransactionTest, ConnectedCallbackCalledAsyncError) {
+  MockRead data_reads[] = {
+      MockRead("HTTP/1.0 200 OK\r\n"),
+      MockRead(SYNCHRONOUS, OK),
+  };
+  StaticSocketDataProvider data(data_reads, base::span<MockWrite>());
+  session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+  ConnectedHandler connected_handler;
+  connected_handler.set_run_callback(true);
+  connected_handler.set_result(ERR_FAILED);
+  std::unique_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
+  auto request = DefaultRequestInfo();
+  HttpNetworkTransaction transaction(DEFAULT_PRIORITY, session.get());
+  transaction.SetConnectedCallback(connected_handler.Callback());
+
+  TestCompletionCallback callback;
+  EXPECT_THAT(
+      transaction.Start(&request, callback.callback(), NetLogWithSource()),
+      IsError(ERR_IO_PENDING));
+  EXPECT_THAT(callback.WaitForResult(), IsError(ERR_FAILED));
+
+  EXPECT_THAT(connected_handler.transports(),
+              ElementsAre(EmbeddedHttpServerTransportInfo()));
+}
+
 // Allow up to 4 bytes of junk to precede status line.
 TEST_F(HttpNetworkTransactionTest, StatusLineJunk3Bytes) {
   MockRead data_reads[] = {
diff --git a/net/http/http_transaction.h b/net/http/http_transaction.h
index e3a44d98..924344fb 100644
--- a/net/http/http_transaction.h
+++ b/net/http/http_transaction.h
@@ -8,6 +8,7 @@
 #include <stdint.h>
 
 #include "net/base/completion_once_callback.h"
+#include "net/base/completion_repeating_callback.h"
 #include "net/base/load_states.h"
 #include "net/base/net_error_details.h"
 #include "net/base/net_export.h"
@@ -55,12 +56,9 @@
   // authentication is required. We should notify this callback that a
   // connection was established, even though the stream might not be ready for
   // us to send data through it.
-  //
-  // TODO(crbug.com/591068): Allow ERR_IO_PENDING, add a new state machine state
-  // to wait on a callback (either passed to this callback or a new explicit
-  // method like ResumeNetworkStart()) to be called before continuing.
   using ConnectedCallback =
-      base::RepeatingCallback<int(const TransportInfo& info)>;
+      base::RepeatingCallback<int(const TransportInfo& info,
+                                  CompletionOnceCallback callback)>;
 
   // Stops any pending IO and destroys the transaction object.
   virtual ~HttpTransaction() {}
diff --git a/net/http/http_transaction_test_util.cc b/net/http/http_transaction_test_util.cc
index 47d8b84..936cdd3 100644
--- a/net/http/http_transaction_test_util.cc
+++ b/net/http/http_transaction_test_util.cc
@@ -550,7 +550,8 @@
 
   int result = OK;
   if (!connected_callback_.is_null()) {
-    result = connected_callback_.Run(t->transport_info);
+    result = connected_callback_.Run(t->transport_info,
+                                     base::DoNothing::Repeatedly<int>());
   }
 
   CallbackLater(std::move(callback), result);
@@ -687,8 +688,14 @@
 ConnectedHandler::ConnectedHandler(ConnectedHandler&&) = default;
 ConnectedHandler& ConnectedHandler::operator=(ConnectedHandler&&) = default;
 
-int ConnectedHandler::OnConnected(const TransportInfo& info) {
+int ConnectedHandler::OnConnected(const TransportInfo& info,
+                                  CompletionOnceCallback callback) {
   transports_.push_back(info);
+  if (run_callback_) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), result_));
+    return net::ERR_IO_PENDING;
+  }
   return result_;
 }
 
diff --git a/net/http/http_transaction_test_util.h b/net/http/http_transaction_test_util.h
index 4c4f7af..fee5ca6 100644
--- a/net/http/http_transaction_test_util.h
+++ b/net/http/http_transaction_test_util.h
@@ -400,7 +400,7 @@
 
   // Compatible with HttpTransaction::ConnectedCallback.
   // Returns the last value passed to set_result(), if any, OK otherwise.
-  int OnConnected(const TransportInfo& info);
+  int OnConnected(const TransportInfo& info, CompletionOnceCallback callback);
 
   // Returns the list of arguments with which OnConnected() was called.
   // The arguments are listed in the same order as the calls were received.
@@ -409,9 +409,15 @@
   // Sets the value to be returned by subsequent calls to OnConnected().
   void set_result(int result) { result_ = result; }
 
+  // If true, runs the callback supplied to OnConnected asynchronously with
+  // `result_`. Otherwise, the callback is skipped and `result_` is returned
+  // directly.
+  void set_run_callback(bool run_callback) { run_callback_ = run_callback; }
+
  private:
   std::vector<TransportInfo> transports_;
   int result_ = OK;
+  bool run_callback_ = false;
 };
 
 }  // namespace net
diff --git a/net/tools/testserver/testserver.pydeps b/net/tools/testserver/testserver.pydeps
index e6b32e3..58032b23 100644
--- a/net/tools/testserver/testserver.pydeps
+++ b/net/tools/testserver/testserver.pydeps
@@ -1,5 +1,5 @@
 # Generated by running:
-#   build/print_python_deps.py --root net/tools/testserver --output net/tools/testserver/testserver.pydeps --whitelist third_party/tlslite/tlslite/utils net/tools/testserver/testserver.py
+#   build/print_python_deps.py --root net/tools/testserver --output net/tools/testserver/testserver.pydeps --allowlist third_party/tlslite/tlslite/utils net/tools/testserver/testserver.py
 ../../../third_party/pywebsocket3/src/mod_pywebsocket/__init__.py
 ../../../third_party/pywebsocket3/src/mod_pywebsocket/_stream_exceptions.py
 ../../../third_party/pywebsocket3/src/mod_pywebsocket/common.py
diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
index 93cf953..0931be2 100644
--- a/net/url_request/url_request.cc
+++ b/net/url_request/url_request.cc
@@ -24,6 +24,7 @@
 #include "net/base/network_change_notifier.h"
 #include "net/base/network_delegate.h"
 #include "net/base/upload_data_stream.h"
+#include "net/http/http_log_util.h"
 #include "net/http/http_util.h"
 #include "net/log/net_log.h"
 #include "net/log/net_log_event_type.h"
@@ -131,7 +132,8 @@
 // URLRequest::Delegate
 
 int URLRequest::Delegate::OnConnected(URLRequest* request,
-                                      const TransportInfo& info) {
+                                      const TransportInfo& info,
+                                      CompletionOnceCallback callback) {
   return OK;
 }
 
@@ -776,8 +778,9 @@
   return (status_ != OK && status_ != ERR_IO_PENDING);
 }
 
-int URLRequest::NotifyConnected(const TransportInfo& info) {
-  return delegate_->OnConnected(this, info);
+int URLRequest::NotifyConnected(const TransportInfo& info,
+                                CompletionOnceCallback callback) {
+  return delegate_->OnConnected(this, info, std::move(callback));
 }
 
 void URLRequest::NotifyReceivedRedirect(const RedirectInfo& redirect_info,
diff --git a/net/url_request/url_request.h b/net/url_request/url_request.h
index 31199b8..8842d2e1 100644
--- a/net/url_request/url_request.h
+++ b/net/url_request/url_request.h
@@ -19,6 +19,7 @@
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
 #include "net/base/auth.h"
+#include "net/base/completion_repeating_callback.h"
 #include "net/base/idempotency.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/isolation_info.h"
@@ -126,13 +127,12 @@
     // several times per transaction, e.g. if the connection is retried, after
     // each HTTP auth challenge, or for split HTTP range requests.
     //
-    // If this returns an error, the request fails with the given error.
-    // Otherwise the request continues unimpeded.
-    // Must not return ERR_IO_PENDING.
-    //
-    // TODO(crbug.com/591068): Allow ERR_IO_PENDING for a potentially-slow
-    // CORS-RFC1918 preflight check.
-    virtual int OnConnected(URLRequest* request, const TransportInfo& info);
+    // If this returns an error, the transaction will stop. The transaction
+    // will continue when the |callback| is run. If run with an error, the
+    // transaction will fail.
+    virtual int OnConnected(URLRequest* request,
+                            const TransportInfo& info,
+                            CompletionOnceCallback callback);
 
     // Called upon receiving a redirect.  The delegate may call the request's
     // Cancel method to prevent the redirect from being followed.  Since there
@@ -805,7 +805,8 @@
 
   // These functions delegate to |delegate_|.  See URLRequest::Delegate for the
   // meaning of these functions.
-  int NotifyConnected(const TransportInfo& info);
+  int NotifyConnected(const TransportInfo& info,
+                      CompletionOnceCallback callback);
   void NotifyAuthRequired(std::unique_ptr<AuthChallengeInfo> auth_info);
   void NotifyCertificateRequested(SSLCertRequestInfo* cert_request_info);
   void NotifySSLCertificateError(int net_error,
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 2b98e38..5f46559 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -322,8 +322,10 @@
   transaction_->CloseConnectionOnDestruction();
 }
 
-int URLRequestHttpJob::NotifyConnectedCallback(const TransportInfo& info) {
-  return URLRequestJob::NotifyConnected(info);
+int URLRequestHttpJob::NotifyConnectedCallback(
+    const TransportInfo& info,
+    CompletionOnceCallback callback) {
+  return URLRequestJob::NotifyConnected(info, std::move(callback));
 }
 
 void URLRequestHttpJob::NotifyHeadersComplete() {
diff --git a/net/url_request/url_request_http_job.h b/net/url_request/url_request_http_job.h
index c026834a..1ad9a378 100644
--- a/net/url_request/url_request_http_job.h
+++ b/net/url_request/url_request_http_job.h
@@ -125,7 +125,8 @@
   // This just forwards the call to URLRequestJob::NotifyConnected().
   // We need it because that method is protected and cannot be bound in a
   // callback in this class.
-  int NotifyConnectedCallback(const TransportInfo& info);
+  int NotifyConnectedCallback(const TransportInfo& info,
+                              CompletionOnceCallback callback);
 
   void RestartTransactionWithAuth(const AuthCredentials& credentials);
 
diff --git a/net/url_request/url_request_job.cc b/net/url_request/url_request_job.cc
index 9eaf309..42e3e3c 100644
--- a/net/url_request/url_request_job.cc
+++ b/net/url_request/url_request_job.cc
@@ -385,8 +385,9 @@
   return GURL();
 }
 
-int URLRequestJob::NotifyConnected(const TransportInfo& info) {
-  return request_->NotifyConnected(info);
+int URLRequestJob::NotifyConnected(const TransportInfo& info,
+                                   CompletionOnceCallback callback) {
+  return request_->NotifyConnected(info, std::move(callback));
 }
 
 void URLRequestJob::NotifyCertificateRequested(
diff --git a/net/url_request/url_request_job.h b/net/url_request/url_request_job.h
index 065794b..29b381d 100644
--- a/net/url_request/url_request_job.h
+++ b/net/url_request/url_request_job.h
@@ -15,6 +15,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "net/base/completion_once_callback.h"
+#include "net/base/completion_repeating_callback.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/load_states.h"
 #include "net/base/net_error_details.h"
@@ -260,7 +261,8 @@
 
  protected:
   // Notifies the job that we are connected.
-  int NotifyConnected(const TransportInfo& info);
+  int NotifyConnected(const TransportInfo& info,
+                      CompletionOnceCallback callback);
 
   // Notifies the job that a certificate is requested.
   void NotifyCertificateRequested(SSLCertRequestInfo* cert_request_info);
diff --git a/net/url_request/url_request_test_util.cc b/net/url_request/url_request_test_util.cc
index d0a70f24..6478f7e 100644
--- a/net/url_request/url_request_test_util.cc
+++ b/net/url_request/url_request_test_util.cc
@@ -242,8 +242,17 @@
   run_loop.Run();
 }
 
-int TestDelegate::OnConnected(URLRequest* request, const TransportInfo& info) {
+int TestDelegate::OnConnected(URLRequest* request,
+                              const TransportInfo& info,
+                              CompletionOnceCallback callback) {
   transports_.push_back(info);
+
+  if (on_connected_run_callback_) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), on_connected_result_));
+    return net::ERR_IO_PENDING;
+  }
+
   return on_connected_result_;
 }
 
diff --git a/net/url_request/url_request_test_util.h b/net/url_request/url_request_test_util.h
index 7cfed610..76d96379 100644
--- a/net/url_request/url_request_test_util.h
+++ b/net/url_request/url_request_test_util.h
@@ -186,6 +186,12 @@
     credentials_ = credentials;
   }
 
+  // If true, the delegate will asynchronously run the callback passed in from
+  // URLRequest with `on_connected_result_`
+  void set_on_connected_run_callback(bool run_callback) {
+    on_connected_run_callback_ = run_callback;
+  }
+
   // Returns the list of arguments with which OnConnected() was called.
   // The arguments are listed in the same order as the calls were received.
   const std::vector<TransportInfo>& transports() const { return transports_; }
@@ -211,7 +217,9 @@
   int request_status() const { return request_status_; }
 
   // URLRequest::Delegate:
-  int OnConnected(URLRequest* request, const TransportInfo& info) override;
+  int OnConnected(URLRequest* request,
+                  const TransportInfo& info,
+                  CompletionOnceCallback callback) override;
   void OnReceivedRedirect(URLRequest* request,
                           const RedirectInfo& redirect_info,
                           bool* defer_redirect) override;
@@ -271,6 +279,8 @@
   scoped_refptr<IOBuffer> buf_;
 
   RedirectInfo redirect_info_;
+
+  bool on_connected_run_callback_ = false;
 };
 
 //-----------------------------------------------------------------------------
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 324ec9cb..6dfc872 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -13099,4 +13099,36 @@
               testing::ElementsAre("www.example.test"));
 }
 
+TEST_F(URLRequestTest, OnConnectedCallbackAsyncOK) {
+  HttpTestServer test_server;
+  ASSERT_TRUE(test_server.Start());
+
+  TestURLRequestContext context;
+  TestDelegate d;
+  d.set_on_connected_run_callback(true);
+  d.set_on_connected_result(OK);
+  std::unique_ptr<URLRequest> req(context.CreateFirstPartyRequest(
+      test_server.GetURL("/defaultresponse"), DEFAULT_PRIORITY, &d,
+      TRAFFIC_ANNOTATION_FOR_TESTS));
+  req->Start();
+  d.RunUntilComplete();
+  EXPECT_THAT(d.request_status(), IsOk());
+}
+
+TEST_F(URLRequestTest, OnConnectedCallbackAsyncError) {
+  HttpTestServer test_server;
+  ASSERT_TRUE(test_server.Start());
+
+  TestURLRequestContext context;
+  TestDelegate d;
+  d.set_on_connected_run_callback(true);
+  d.set_on_connected_result(ERR_FAILED);
+  std::unique_ptr<URLRequest> req(context.CreateFirstPartyRequest(
+      test_server.GetURL("/defaultresponse"), DEFAULT_PRIORITY, &d,
+      TRAFFIC_ANNOTATION_FOR_TESTS));
+  req->Start();
+  d.RunUntilComplete();
+  EXPECT_THAT(d.request_status(), IsError(ERR_FAILED));
+}
+
 }  // namespace net
diff --git a/sandbox/win/src/broker_services.cc b/sandbox/win/src/broker_services.cc
index 9f3d53958..4cd687a 100644
--- a/sandbox/win/src/broker_services.cc
+++ b/sandbox/win/src/broker_services.cc
@@ -139,6 +139,9 @@
 
   no_targets_.Set(::CreateEventW(nullptr, true, false, nullptr));
 
+  // The thread pool is shared by all the targets.
+  thread_pool_ = std::make_unique<Win2kThreadPool>();
+
 #if defined(ARCH_CPU_32_BITS)
   // Conserve address space in 32-bit Chrome. This thread uses a small and
   // consistent amount and doesn't need the default of 1.5 MiB.
@@ -482,11 +485,6 @@
   if (!startup_info->BuildStartupInformation())
     return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
 
-  // Construct the thread pool here in case it is expensive.
-  // The thread pool is shared by all the targets
-  if (!thread_pool_)
-    thread_pool_ = std::make_unique<Win2kThreadPool>();
-
   // Create the TargetProcess object and spawn the target suspended. Note that
   // Brokerservices does not own the target object. It is owned by the Policy.
   base::win::ScopedProcessInformation process_info;
diff --git a/services/device/public/cpp/geolocation/BUILD.gn b/services/device/public/cpp/geolocation/BUILD.gn
index f1bc541..9502e6f 100644
--- a/services/device/public/cpp/geolocation/BUILD.gn
+++ b/services/device/public/cpp/geolocation/BUILD.gn
@@ -2,13 +2,15 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-source_set("geolocation") {
+component("geolocation") {
   sources = [
     "geoposition.cc",
     "geoposition.h",
     "location_provider.h",
     "location_system_permission_status.h",
   ]
+  defines = [ "IS_GEOLOCATION_IMPL=1" ]
+  output_name = "geolocation_service"
   if (is_mac) {
     frameworks = [ "CoreLocation.framework" ]
     sources += [
diff --git a/services/device/public/cpp/geolocation/geolocation_system_permission_mac.h b/services/device/public/cpp/geolocation/geolocation_system_permission_mac.h
index 3758d0a..a310bae 100644
--- a/services/device/public/cpp/geolocation/geolocation_system_permission_mac.h
+++ b/services/device/public/cpp/geolocation/geolocation_system_permission_mac.h
@@ -6,6 +6,7 @@
 #define SERVICES_DEVICE_PUBLIC_CPP_GEOLOCATION_GEOLOCATION_SYSTEM_PERMISSION_MAC_H_
 
 #include "base/callback.h"
+#include "base/component_export.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/observer_list_threadsafe.h"
@@ -15,7 +16,7 @@
 
 // This class is owned by the browser process and keeps track of the macOS
 // location permissions for the browser.
-class GeolocationSystemPermissionManager {
+class COMPONENT_EXPORT(GEOLOCATION) GeolocationSystemPermissionManager {
  public:
   class GeolocationPermissionObserver : public base::CheckedObserver {
    public:
@@ -43,4 +44,4 @@
 
 }  // namespace device
 
-#endif  // SERVICES_DEVICE_PUBLIC_CPP_GEOLOCATION_GEOLOCATION_SYSTEM_PERMISSION_MAC_H_
\ No newline at end of file
+#endif  // SERVICES_DEVICE_PUBLIC_CPP_GEOLOCATION_GEOLOCATION_SYSTEM_PERMISSION_MAC_H_
diff --git a/services/device/public/cpp/geolocation/geoposition.h b/services/device/public/cpp/geolocation/geoposition.h
index 791f8e6..f1f62f9f 100644
--- a/services/device/public/cpp/geolocation/geoposition.h
+++ b/services/device/public/cpp/geolocation/geoposition.h
@@ -5,11 +5,13 @@
 #ifndef SERVICES_DEVICE_PUBLIC_CPP_GEOLOCATION_GEOPOSITION_H_
 #define SERVICES_DEVICE_PUBLIC_CPP_GEOLOCATION_GEOPOSITION_H_
 
+#include "base/component_export.h"
 #include "services/device/public/mojom/geoposition.mojom.h"
 
 namespace device {
 
-bool ValidateGeoposition(const mojom::Geoposition& position);
+bool COMPONENT_EXPORT(GEOLOCATION)
+    ValidateGeoposition(const mojom::Geoposition& position);
 
 }  // namespace device
 
diff --git a/services/device/public/cpp/geolocation/location_system_permission_status.h b/services/device/public/cpp/geolocation/location_system_permission_status.h
index 215d31d..51c5bb0a 100644
--- a/services/device/public/cpp/geolocation/location_system_permission_status.h
+++ b/services/device/public/cpp/geolocation/location_system_permission_status.h
@@ -17,4 +17,4 @@
 
 }  // namespace device
 
-#endif  // SERVICES_DEVICE_PUBLIC_CPP_GEOLOCATION_LOCATION_SYSTEM_PERMISSION_STATUS_H_
\ No newline at end of file
+#endif  // SERVICES_DEVICE_PUBLIC_CPP_GEOLOCATION_LOCATION_SYSTEM_PERMISSION_STATUS_H_
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn
index 9599368..71da6e4 100644
--- a/services/network/BUILD.gn
+++ b/services/network/BUILD.gn
@@ -74,8 +74,6 @@
     "network_service_network_delegate.h",
     "network_service_proxy_delegate.cc",
     "network_service_proxy_delegate.h",
-    "network_usage_accumulator.cc",
-    "network_usage_accumulator.h",
     "origin_policy/origin_policy_constants.h",
     "origin_policy/origin_policy_fetcher.cc",
     "origin_policy/origin_policy_fetcher.h",
@@ -261,8 +259,8 @@
     sources += [
       "expect_ct_reporter.cc",
       "expect_ct_reporter.h",
-      "sct_auditing_cache.cc",
-      "sct_auditing_cache.h",
+      "sct_auditing/sct_auditing_cache.cc",
+      "sct_auditing/sct_auditing_cache.h",
     ]
     deps += [
       "//components/certificate_transparency",
@@ -325,7 +323,6 @@
     "network_quality_estimator_manager_unittest.cc",
     "network_service_proxy_delegate_unittest.cc",
     "network_service_unittest.cc",
-    "network_usage_accumulator_unittest.cc",
     "origin_policy/origin_policy_manager_unittest.cc",
     "origin_policy/origin_policy_parsed_header_unittest.cc",
     "origin_policy/origin_policy_parser_unittest.cc",
@@ -430,7 +427,7 @@
   if (is_ct_supported) {
     sources += [
       "expect_ct_reporter_unittest.cc",
-      "sct_auditing_cache_unittest.cc",
+      "sct_auditing/sct_auditing_cache_unittest.cc",
     ]
     deps += [ "//components/certificate_transparency" ]
   }
@@ -461,8 +458,6 @@
     "test/test_network_context_client.h",
     "test/test_network_quality_tracker.cc",
     "test/test_network_quality_tracker.h",
-    "test/test_network_service_client.cc",
-    "test/test_network_service_client.h",
     "test/test_resource_scheduler.h",
     "test/test_shared_url_loader_factory.cc",
     "test/test_shared_url_loader_factory.h",
diff --git a/services/network/OWNERS b/services/network/OWNERS
index adc8775..2fa0716 100644
--- a/services/network/OWNERS
+++ b/services/network/OWNERS
@@ -35,6 +35,7 @@
 
 per-file BUILD.gn=file://net/nqe/OWNERS
 per-file BUILD.gn=file://services/network/resource_scheduler/OWNERS
+per-file BUILD.gn=file://services/network/sct_auditing/OWNERS
 
 # Content Security Policy
 per-file content_security_policy*=lfg@chromium.org
diff --git a/services/network/cors/preflight_controller_unittest.cc b/services/network/cors/preflight_controller_unittest.cc
index 49b5a19..2e11fea 100644
--- a/services/network/cors/preflight_controller_unittest.cc
+++ b/services/network/cors/preflight_controller_unittest.cc
@@ -25,7 +25,6 @@
 #include "services/network/public/mojom/network_service.mojom.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "services/network/test/fake_test_cert_verifier_params_factory.h"
-#include "services/network/test/test_network_service_client.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/origin.h"
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 71f4a3b0c..8e53d65 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -83,7 +83,6 @@
 #include "services/network/network_service.h"
 #include "services/network/network_service_network_delegate.h"
 #include "services/network/network_service_proxy_delegate.h"
-#include "services/network/network_usage_accumulator.h"
 #include "services/network/p2p/socket_manager.h"
 #include "services/network/proxy_config_service_mojo.h"
 #include "services/network/proxy_lookup_request.h"
@@ -125,7 +124,7 @@
 #include "net/cert/ct_log_verifier.h"
 #include "net/cert/multi_log_ct_verifier.h"
 #include "services/network/expect_ct_reporter.h"
-#include "services/network/sct_auditing_cache.h"
+#include "services/network/sct_auditing/sct_auditing_cache.h"
 #endif  // BUILDFLAG(IS_CT_SUPPORTED)
 
 #if !BUILDFLAG(DISABLE_FTP_SUPPORT)
@@ -608,10 +607,6 @@
     const url::Origin& origin,
     const net::IsolationInfo& isolation_info,
     mojo::PendingRemote<mojom::CookieAccessObserver> cookie_observer) {
-  mojom::NetworkServiceClient* network_service_client = nullptr;
-  if (network_service())
-    network_service_client = network_service()->client();
-
   restricted_cookie_manager_receivers_.Add(
       std::make_unique<RestrictedCookieManager>(
           role, url_request_context_->cookie_store(),
@@ -705,23 +700,9 @@
 
 void NetworkContext::DestroyURLLoaderFactory(
     cors::CorsURLLoaderFactory* url_loader_factory) {
-  const int32_t process_id = url_loader_factory->process_id();
-
   auto it = url_loader_factories_.find(url_loader_factory);
   DCHECK(it != url_loader_factories_.end());
   url_loader_factories_.erase(it);
-
-  // Reset bytes transferred for the process if |url_loader_factory| is the
-  // last factory associated with the process.
-  if (network_service() &&
-      std::none_of(url_loader_factories_.cbegin(), url_loader_factories_.cend(),
-                   [process_id](const auto& factory) {
-                     return factory->process_id() == process_id;
-                   })) {
-    network_service()
-        ->network_usage_accumulator()
-        ->ClearBytesTransferredForProcess(process_id);
-  }
 }
 
 void NetworkContext::Remove(QuicTransport* transport) {
@@ -1236,8 +1217,10 @@
     const net::X509Certificate* validated_certificate_chain,
     const net::SignedCertificateTimestampAndStatusList&
         signed_certificate_timestamps) {
+  if (!this->is_sct_auditing_enabled())
+    return;
   network_service()->sct_auditing_cache()->MaybeEnqueueReport(
-      this, host_port_pair, validated_certificate_chain,
+      host_port_pair, validated_certificate_chain,
       signed_certificate_timestamps);
 }
 
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
index 3bf0a82..8d3a7ed 100644
--- a/services/network/network_service.cc
+++ b/services/network/network_service.cc
@@ -58,7 +58,6 @@
 #include "services/network/net_log_exporter.h"
 #include "services/network/net_log_proxy_sink.h"
 #include "services/network/network_context.h"
-#include "services/network/network_usage_accumulator.h"
 #include "services/network/public/cpp/crash_keys.h"
 #include "services/network/public/cpp/cross_origin_read_blocking.h"
 #include "services/network/public/cpp/features.h"
@@ -83,7 +82,7 @@
 #endif
 
 #if BUILDFLAG(IS_CT_SUPPORTED)
-#include "services/network/sct_auditing_cache.h"
+#include "services/network/sct_auditing/sct_auditing_cache.h"
 #endif
 
 namespace network {
@@ -329,8 +328,6 @@
       net::NetworkChangeNotifier::GetSystemDnsConfigNotifier(), net_log_);
   host_resolver_factory_ = std::make_unique<net::HostResolver::Factory>();
 
-  network_usage_accumulator_ = std::make_unique<NetworkUsageAccumulator>();
-
   http_auth_cache_copier_ = std::make_unique<HttpAuthCacheCopier>();
 
   crl_set_distributor_ = std::make_unique<CRLSetDistributor>();
@@ -339,6 +336,11 @@
 
   trust_token_key_commitments_ = std::make_unique<TrustTokenKeyCommitments>();
 
+  if (params->default_observer) {
+    default_url_loader_network_service_observer_.Bind(
+        std::move(params->default_observer));
+  }
+
   first_party_sets_ = std::make_unique<FirstPartySets>();
   if (net::cookie_util::IsFirstPartySetsEnabled() &&
       command_line->HasSwitch(switches::kUseFirstPartySet)) {
@@ -436,10 +438,7 @@
   return net::CreateNetLogEntriesForActiveObjects(contexts, observer);
 }
 
-void NetworkService::SetClient(
-    mojo::PendingRemote<mojom::NetworkServiceClient> client,
-    mojom::NetworkServiceParamsPtr params) {
-  client_.Bind(std::move(client));
+void NetworkService::SetParams(mojom::NetworkServiceParamsPtr params) {
   Initialize(std::move(params));
 }
 
@@ -599,11 +598,6 @@
   dns_config_change_manager_->AddReceiver(std::move(receiver));
 }
 
-void NetworkService::GetTotalNetworkUsages(
-    mojom::NetworkService::GetTotalNetworkUsagesCallback callback) {
-  std::move(callback).Run(network_usage_accumulator_->GetTotalNetworkUsages());
-}
-
 void NetworkService::GetNetworkList(
     uint32_t policy,
     mojom::NetworkService::GetNetworkListCallback callback) {
@@ -794,6 +788,13 @@
   receiver_.Bind(std::move(receiver));
 }
 
+mojom::URLLoaderNetworkServiceObserver*
+NetworkService::GetDefaultURLLoaderNetworkServiceObserver() {
+  if (default_url_loader_network_service_observer_)
+    return default_url_loader_network_service_observer_.get();
+  return nullptr;
+}
+
 // static
 NetworkService* NetworkService::GetNetworkServiceForTesting() {
   return g_network_service;
diff --git a/services/network/network_service.h b/services/network/network_service.h
index 354ab89..3ab3e51 100644
--- a/services/network/network_service.h
+++ b/services/network/network_service.h
@@ -64,7 +64,6 @@
 class NetLogProxySink;
 class NetworkContext;
 class NetworkService;
-class NetworkUsageAccumulator;
 class SCTAuditingCache;
 
 class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService
@@ -115,8 +114,7 @@
       net::NetLog::ThreadSafeObserver* observer);
 
   // mojom::NetworkService implementation:
-  void SetClient(mojo::PendingRemote<mojom::NetworkServiceClient> client,
-                 mojom::NetworkServiceParamsPtr params) override;
+  void SetParams(mojom::NetworkServiceParamsPtr params) override;
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   void ReinitializeLogging(mojom::LoggingSettingsPtr settings) override;
 #endif
@@ -150,8 +148,6 @@
       override;
   void GetDnsConfigChangeManager(
       mojo::PendingReceiver<mojom::DnsConfigChangeManager> receiver) override;
-  void GetTotalNetworkUsages(
-      mojom::NetworkService::GetTotalNetworkUsagesCallback callback) override;
   void GetNetworkList(
       uint32_t policy,
       mojom::NetworkService::GetNetworkListCallback callback) override;
@@ -206,9 +202,6 @@
   bool IsInitiatorAllowedForPlugin(int process_id,
                                    const url::Origin& request_initiator);
 
-  mojom::NetworkServiceClient* client() {
-    return client_.is_bound() ? client_.get() : nullptr;
-  }
   net::NetworkQualityEstimator* network_quality_estimator() {
     return network_quality_estimator_manager_->GetNetworkQualityEstimator();
   }
@@ -222,9 +215,6 @@
   net::HostResolver::Factory* host_resolver_factory() {
     return host_resolver_factory_.get();
   }
-  NetworkUsageAccumulator* network_usage_accumulator() {
-    return network_usage_accumulator_.get();
-  }
   HttpAuthCacheCopier* http_auth_cache_copier() {
     return http_auth_cache_copier_.get();
   }
@@ -260,6 +250,9 @@
   SCTAuditingCache* sct_auditing_cache() { return sct_auditing_cache_.get(); }
 #endif
 
+  mojom::URLLoaderNetworkServiceObserver*
+  GetDefaultURLLoaderNetworkServiceObserver();
+
   static NetworkService* GetNetworkServiceForTesting();
 
  private:
@@ -280,8 +273,6 @@
   std::unique_ptr<net::FileNetLogObserver> file_net_log_observer_;
   net::TraceNetLogObserver trace_net_log_observer_;
 
-  mojo::Remote<mojom::NetworkServiceClient> client_;
-
   KeepaliveStatisticsRecorder keepalive_statistics_recorder_;
 
   std::unique_ptr<NetworkChangeManager> network_change_manager_;
@@ -296,6 +287,9 @@
 
   mojo::Receiver<mojom::NetworkService> receiver_{this};
 
+  mojo::Remote<mojom::URLLoaderNetworkServiceObserver>
+      default_url_loader_network_service_observer_;
+
   std::unique_ptr<NetworkQualityEstimatorManager>
       network_quality_estimator_manager_;
 
@@ -303,7 +297,6 @@
 
   std::unique_ptr<net::HostResolverManager> host_resolver_manager_;
   std::unique_ptr<net::HostResolver::Factory> host_resolver_factory_;
-  std::unique_ptr<NetworkUsageAccumulator> network_usage_accumulator_;
   std::unique_ptr<HttpAuthCacheCopier> http_auth_cache_copier_;
 
   // Members that would store the http auth network_service related params.
diff --git a/services/network/network_service_unittest.cc b/services/network/network_service_unittest.cc
index c8dcb99c..a0db7c7 100644
--- a/services/network/network_service_unittest.cc
+++ b/services/network/network_service_unittest.cc
@@ -59,7 +59,6 @@
 #include "services/network/public/mojom/network_service.mojom.h"
 #include "services/network/test/fake_test_cert_verifier_params_factory.h"
 #include "services/network/test/test_network_context_client.h"
-#include "services/network/test/test_network_service_client.h"
 #include "services/network/test/test_url_loader_client.h"
 #include "services/network/test/test_url_loader_network_observer.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/services/network/network_usage_accumulator.cc b/services/network/network_usage_accumulator.cc
deleted file mode 100644
index 46baa61..0000000
--- a/services/network/network_usage_accumulator.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 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 "services/network/network_usage_accumulator.h"
-
-namespace network {
-
-struct NetworkUsageAccumulator::NetworkUsageParam {
-  int64_t total_bytes_received = 0;
-  int64_t total_bytes_sent = 0;
-};
-
-NetworkUsageAccumulator::NetworkUsageAccumulator() = default;
-
-NetworkUsageAccumulator::~NetworkUsageAccumulator() = default;
-
-void NetworkUsageAccumulator::OnBytesTransferred(uint32_t process_id,
-                                                 uint32_t routing_id,
-                                                 int64_t bytes_received,
-                                                 int64_t bytes_sent) {
-  auto& entry = total_network_usages_[process_id][routing_id];
-  entry.total_bytes_received += bytes_received;
-  entry.total_bytes_sent += bytes_sent;
-}
-
-std::vector<mojom::NetworkUsagePtr>
-NetworkUsageAccumulator::GetTotalNetworkUsages() const {
-  std::vector<mojom::NetworkUsagePtr> total_network_usages;
-  for (const auto& process_iter : total_network_usages_) {
-    for (const auto& routing_iter : process_iter.second) {
-      auto usage = mojom::NetworkUsage::New();
-      usage->process_id = process_iter.first;
-      usage->routing_id = routing_iter.first;
-      usage->total_bytes_received = routing_iter.second.total_bytes_received;
-      usage->total_bytes_sent = routing_iter.second.total_bytes_sent;
-      total_network_usages.push_back(std::move(usage));
-    }
-  }
-  return total_network_usages;
-}
-
-void NetworkUsageAccumulator::ClearBytesTransferredForProcess(
-    uint32_t process_id) {
-  total_network_usages_.erase(process_id);
-}
-
-}  // namespace network
diff --git a/services/network/network_usage_accumulator.h b/services/network/network_usage_accumulator.h
deleted file mode 100644
index ddb9f415..0000000
--- a/services/network/network_usage_accumulator.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2018 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 SERVICES_NETWORK_NETWORK_USAGE_ACCUMULATOR_H_
-#define SERVICES_NETWORK_NETWORK_USAGE_ACCUMULATOR_H_
-
-#include <map>
-
-#include "base/component_export.h"
-#include "base/containers/flat_map.h"
-#include "base/containers/small_map.h"
-#include "base/memory/weak_ptr.h"
-#include "services/network/public/mojom/network_service.mojom.h"
-
-namespace network {
-
-// NetworkUsageAccumulator keeps track of total network usage for active
-// consumers.
-class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkUsageAccumulator
-    : public base::SupportsWeakPtr<NetworkUsageAccumulator> {
- public:
-  NetworkUsageAccumulator();
-  ~NetworkUsageAccumulator();
-
-  // Reports raw network usage to this accumulator.
-  void OnBytesTransferred(uint32_t process_id,
-                          uint32_t routing_id,
-                          int64_t bytes_received,
-                          int64_t bytes_sent);
-
-  // Marks a process as inactive and remove the entry.
-  void ClearBytesTransferredForProcess(uint32_t process_id);
-
-  // Returns the accumulated network usage for active consumers.
-  std::vector<mojom::NetworkUsagePtr> GetTotalNetworkUsages() const;
-
- private:
-  struct NetworkUsageParam;
-
-  // Number of active processes is usually small, but could go as high as
-  // |PhysicalMemoryMB / 80|.
-  // See |RenderProcessHost::GetMaxRendererProcessCount()|.
-  base::small_map<
-      std::map<uint32_t /* process_id */,
-               base::flat_map<uint32_t /* routing_id */, NetworkUsageParam>>>
-      total_network_usages_;
-
-  DISALLOW_COPY_AND_ASSIGN(NetworkUsageAccumulator);
-};
-
-}  // namespace network
-
-#endif  // SERVICES_NETWORK_NETWORK_USAGE_ACCUMULATOR_H_
diff --git a/services/network/network_usage_accumulator_unittest.cc b/services/network/network_usage_accumulator_unittest.cc
deleted file mode 100644
index 5e6df9c..0000000
--- a/services/network/network_usage_accumulator_unittest.cc
+++ /dev/null
@@ -1,348 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "services/network/network_usage_accumulator.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace network {
-
-namespace {
-
-struct BytesTransferredKey {
-  int32_t process_id;
-  int32_t routing_id;
-};
-
-}  // namespace
-
-class NetworkUsageAccumulatorTest : public testing::Test {
- public:
-  NetworkUsageAccumulatorTest() {}
-  ~NetworkUsageAccumulatorTest() override {}
-
-  void SimulateRawBytesTransferred(const BytesTransferredKey& key,
-                                   int64_t bytes_received,
-                                   int64_t bytes_sent) {
-    network_usage_accumulator_.OnBytesTransferred(
-        key.process_id, key.routing_id, bytes_received, bytes_sent);
-  }
-
-  mojom::NetworkUsage* GetUsageForKey(
-      const std::vector<mojom::NetworkUsagePtr>& usages,
-      const BytesTransferredKey& key) {
-    for (const auto& usage : usages) {
-      if (key.process_id == usage->process_id &&
-          key.routing_id == usage->routing_id)
-        return usage.get();
-    }
-    return nullptr;
-  }
-
-  void ClearBytesTransferredForProcess(int32_t process_id) {
-    network_usage_accumulator_.ClearBytesTransferredForProcess(process_id);
-  }
-
-  std::vector<mojom::NetworkUsagePtr> GetTotalNetworkUsages() const {
-    return network_usage_accumulator_.GetTotalNetworkUsages();
-  }
-
- private:
-  NetworkUsageAccumulator network_usage_accumulator_;
-
-  DISALLOW_COPY_AND_ASSIGN(NetworkUsageAccumulatorTest);
-};
-
-// Tests that the |process_id| and |routing_id| are used in the map correctly.
-TEST_F(NetworkUsageAccumulatorTest, ChildRouteData) {
-  BytesTransferredKey key = {100, 190};
-
-  int64_t correct_read_bytes = 0;
-  int64_t correct_sent_bytes = 0;
-
-  int read_bytes_array[] = {900, 300, 100};
-  int sent_bytes_array[] = {130, 153, 934};
-
-  for (int i : read_bytes_array) {
-    SimulateRawBytesTransferred(key, i, 0);
-    correct_read_bytes += i;
-  }
-  for (int i : sent_bytes_array) {
-    SimulateRawBytesTransferred(key, 0, i);
-    correct_sent_bytes += i;
-  }
-
-  auto returned_usages = GetTotalNetworkUsages();
-  EXPECT_EQ(1U, returned_usages.size());
-  EXPECT_EQ(correct_sent_bytes,
-            GetUsageForKey(returned_usages, key)->total_bytes_sent);
-  EXPECT_EQ(correct_read_bytes,
-            GetUsageForKey(returned_usages, key)->total_bytes_received);
-}
-
-// Tests that two distinct |process_id| and |routing_id| pairs are tracked
-// separately in the unordered map.
-TEST_F(NetworkUsageAccumulatorTest, TwoChildRouteData) {
-  BytesTransferredKey key1 = {32, 1};
-  BytesTransferredKey key2 = {17, 2};
-
-  int64_t correct_read_bytes1 = 0;
-  int64_t correct_sent_bytes1 = 0;
-
-  int64_t correct_read_bytes2 = 0;
-  int64_t correct_sent_bytes2 = 0;
-
-  int read_bytes_array1[] = {453, 987654, 946650};
-  int sent_bytes_array1[] = {138450, 1556473, 954434};
-
-  int read_bytes_array2[] = {905643, 324340, 654150};
-  int sent_bytes_array2[] = {1232138, 157312, 965464};
-
-  for (int i : read_bytes_array1) {
-    SimulateRawBytesTransferred(key1, i, 0);
-    correct_read_bytes1 += i;
-  }
-  for (int i : sent_bytes_array1) {
-    SimulateRawBytesTransferred(key1, 0, i);
-    correct_sent_bytes1 += i;
-  }
-
-  for (int i : read_bytes_array2) {
-    SimulateRawBytesTransferred(key2, i, 0);
-    correct_read_bytes2 += i;
-  }
-  for (int i : sent_bytes_array2) {
-    SimulateRawBytesTransferred(key2, 0, i);
-    correct_sent_bytes2 += i;
-  }
-
-  auto returned_usages = GetTotalNetworkUsages();
-  EXPECT_EQ(2U, returned_usages.size());
-  EXPECT_EQ(correct_sent_bytes1,
-            GetUsageForKey(returned_usages, key1)->total_bytes_sent);
-  EXPECT_EQ(correct_read_bytes1,
-            GetUsageForKey(returned_usages, key1)->total_bytes_received);
-  EXPECT_EQ(correct_sent_bytes2,
-            GetUsageForKey(returned_usages, key2)->total_bytes_sent);
-  EXPECT_EQ(correct_read_bytes2,
-            GetUsageForKey(returned_usages, key2)->total_bytes_received);
-}
-
-// Tests that two keys with the same |process_id| and |routing_id| are tracked
-// together in the accumulator.
-TEST_F(NetworkUsageAccumulatorTest, TwoSameChildRouteData) {
-  BytesTransferredKey key1 = {123, 456};
-  BytesTransferredKey key2 = {123, 456};
-
-  int64_t correct_read_bytes = 0;
-  int64_t correct_sent_bytes = 0;
-
-  int read_bytes_array[] = {90440, 12300, 103420};
-  int sent_bytes_array[] = {44130, 12353, 93234};
-
-  for (int i : read_bytes_array) {
-    SimulateRawBytesTransferred(key1, i, 0);
-    correct_read_bytes += i;
-  }
-  for (int i : sent_bytes_array) {
-    SimulateRawBytesTransferred(key1, 0, i);
-    correct_sent_bytes += i;
-  }
-
-  for (int i : read_bytes_array) {
-    SimulateRawBytesTransferred(key2, i, 0);
-    correct_read_bytes += i;
-  }
-  for (int i : sent_bytes_array) {
-    SimulateRawBytesTransferred(key2, 0, i);
-    correct_sent_bytes += i;
-  }
-
-  auto returned_usages = GetTotalNetworkUsages();
-  EXPECT_EQ(1U, returned_usages.size());
-  EXPECT_EQ(correct_sent_bytes,
-            GetUsageForKey(returned_usages, key1)->total_bytes_sent);
-  EXPECT_EQ(correct_read_bytes,
-            GetUsageForKey(returned_usages, key1)->total_bytes_received);
-  EXPECT_EQ(correct_sent_bytes,
-            GetUsageForKey(returned_usages, key2)->total_bytes_sent);
-  EXPECT_EQ(correct_read_bytes,
-            GetUsageForKey(returned_usages, key2)->total_bytes_received);
-}
-
-// Tests that the map can handle two process_ids with the same routing_id.
-TEST_F(NetworkUsageAccumulatorTest, SameRouteDifferentProcesses) {
-  BytesTransferredKey key1 = {12, 143};
-  BytesTransferredKey key2 = {13, 143};
-
-  int64_t correct_read_bytes1 = 0;
-  int64_t correct_sent_bytes1 = 0;
-
-  int64_t correct_read_bytes2 = 0;
-  int64_t correct_sent_bytes2 = 0;
-
-  int read_bytes_array1[] = {453, 98754, 94650};
-  int sent_bytes_array1[] = {1350, 15643, 95434};
-
-  int read_bytes_array2[] = {905643, 3243, 654150};
-  int sent_bytes_array2[] = {12338, 157312, 9664};
-
-  for (int i : read_bytes_array1) {
-    SimulateRawBytesTransferred(key1, i, 0);
-    correct_read_bytes1 += i;
-  }
-  for (int i : sent_bytes_array1) {
-    SimulateRawBytesTransferred(key1, 0, i);
-    correct_sent_bytes1 += i;
-  }
-
-  for (int i : read_bytes_array2) {
-    SimulateRawBytesTransferred(key2, i, 0);
-    correct_read_bytes2 += i;
-  }
-  for (int i : sent_bytes_array2) {
-    SimulateRawBytesTransferred(key2, 0, i);
-    correct_sent_bytes2 += i;
-  }
-
-  auto returned_usages = GetTotalNetworkUsages();
-  EXPECT_EQ(2U, returned_usages.size());
-  EXPECT_EQ(correct_sent_bytes1,
-            GetUsageForKey(returned_usages, key1)->total_bytes_sent);
-  EXPECT_EQ(correct_read_bytes1,
-            GetUsageForKey(returned_usages, key1)->total_bytes_received);
-  EXPECT_EQ(correct_sent_bytes2,
-            GetUsageForKey(returned_usages, key2)->total_bytes_sent);
-  EXPECT_EQ(correct_read_bytes2,
-            GetUsageForKey(returned_usages, key2)->total_bytes_received);
-}
-
-// Tests that process data is cleared after termination.
-TEST_F(NetworkUsageAccumulatorTest, ClearAfterTermination) {
-  // |key1| and |key2| belongs to the same process.
-  BytesTransferredKey key1 = {100, 190};
-  BytesTransferredKey key2 = {100, 191};
-  BytesTransferredKey key3 = {101, 191};
-
-  // No data has been transferred yet.
-  auto returned_usages = GetTotalNetworkUsages();
-  EXPECT_EQ(0U, returned_usages.size());
-  EXPECT_EQ(nullptr, GetUsageForKey(returned_usages, key1));
-  EXPECT_EQ(nullptr, GetUsageForKey(returned_usages, key2));
-  EXPECT_EQ(nullptr, GetUsageForKey(returned_usages, key3));
-
-  // Simulate data transfer on all three keys.
-  SimulateRawBytesTransferred(key1, 100, 1);
-  SimulateRawBytesTransferred(key2, 2, 200);
-  SimulateRawBytesTransferred(key3, 33, 333);
-  returned_usages = GetTotalNetworkUsages();
-  // Should have data observed on all three keys.
-  EXPECT_EQ(3U, returned_usages.size());
-  EXPECT_NE(nullptr, GetUsageForKey(returned_usages, key1));
-  EXPECT_NE(nullptr, GetUsageForKey(returned_usages, key2));
-  EXPECT_NE(nullptr, GetUsageForKey(returned_usages, key3));
-
-  // Simulate process termination on the first process.
-  ClearBytesTransferredForProcess(key1.process_id);
-
-  // |key1| and |key2| should both be cleared.
-  returned_usages = GetTotalNetworkUsages();
-  EXPECT_EQ(1U, returned_usages.size());
-  EXPECT_EQ(nullptr, GetUsageForKey(returned_usages, key1));
-  EXPECT_EQ(nullptr, GetUsageForKey(returned_usages, key2));
-  // |key3| shouldn't be affected.
-  EXPECT_NE(nullptr, GetUsageForKey(returned_usages, key3));
-}
-
-// Tests that the map can store both types of keys and that it does update after
-// a process has gone.
-TEST_F(NetworkUsageAccumulatorTest, MultipleWavesMixedData) {
-  BytesTransferredKey key1 = {12, 143};
-  BytesTransferredKey key2 = {0, 0};
-
-  int64_t correct_read_bytes1 = 0;
-  int64_t correct_sent_bytes1 = 0;
-
-  int read_bytes_array1[] = {453, 98754, 94650};
-  int sent_bytes_array1[] = {1350, 15643, 95434};
-
-  for (int i : read_bytes_array1) {
-    SimulateRawBytesTransferred(key1, i, 0);
-    correct_read_bytes1 += i;
-  }
-  for (int i : sent_bytes_array1) {
-    SimulateRawBytesTransferred(key1, 0, i);
-    correct_sent_bytes1 += i;
-  }
-
-  auto returned_usages = GetTotalNetworkUsages();
-  EXPECT_NE(nullptr, GetUsageForKey(returned_usages, key1));
-  // |key2| has not been used yet so it shouldn't exist in the returned usages.
-  EXPECT_EQ(nullptr, GetUsageForKey(returned_usages, key2));
-
-  SimulateRawBytesTransferred(key2, 0, 10);
-  returned_usages = GetTotalNetworkUsages();
-  EXPECT_NE(nullptr, GetUsageForKey(returned_usages, key2));
-
-  ClearBytesTransferredForProcess(key1.process_id);
-  ClearBytesTransferredForProcess(key2.process_id);
-
-  correct_sent_bytes1 = 0;
-  correct_read_bytes1 = 0;
-
-  SimulateRawBytesTransferred(key1, 0, 10);
-  correct_sent_bytes1 += 10;
-
-  returned_usages = GetTotalNetworkUsages();
-  EXPECT_EQ(1U, returned_usages.size());
-  EXPECT_EQ(correct_sent_bytes1,
-            GetUsageForKey(returned_usages, key1)->total_bytes_sent);
-  EXPECT_EQ(correct_read_bytes1,
-            GetUsageForKey(returned_usages, key1)->total_bytes_received);
-  // |key2| has been cleared.
-  EXPECT_EQ(nullptr, GetUsageForKey(returned_usages, key2));
-  ClearBytesTransferredForProcess(key1.process_id);
-
-  correct_read_bytes1 = 0;
-  correct_sent_bytes1 = 0;
-
-  int correct_read_bytes2 = 0;
-  int correct_sent_bytes2 = 0;
-
-  int read_bytes_array_second_1[] = {4153, 987154, 946501};
-  int sent_bytes_array_second_1[] = {13510, 115643, 954134};
-
-  int read_bytes_array2[] = {9056243, 32243, 6541250};
-  int sent_bytes_array2[] = {123238, 1527312, 96624};
-
-  for (int i : read_bytes_array_second_1) {
-    SimulateRawBytesTransferred(key1, i, 0);
-    correct_read_bytes1 += i;
-  }
-  for (int i : sent_bytes_array_second_1) {
-    SimulateRawBytesTransferred(key1, 0, i);
-    correct_sent_bytes1 += i;
-  }
-
-  for (int i : read_bytes_array2) {
-    SimulateRawBytesTransferred(key2, i, 0);
-    correct_read_bytes2 += i;
-  }
-  for (int i : sent_bytes_array2) {
-    SimulateRawBytesTransferred(key2, 0, i);
-    correct_sent_bytes2 += i;
-  }
-
-  returned_usages = GetTotalNetworkUsages();
-  EXPECT_EQ(2U, returned_usages.size());
-  EXPECT_EQ(correct_sent_bytes1,
-            GetUsageForKey(returned_usages, key1)->total_bytes_sent);
-  EXPECT_EQ(correct_read_bytes1,
-            GetUsageForKey(returned_usages, key1)->total_bytes_received);
-  EXPECT_EQ(correct_sent_bytes2,
-            GetUsageForKey(returned_usages, key2)->total_bytes_sent);
-  EXPECT_EQ(correct_read_bytes2,
-            GetUsageForKey(returned_usages, key2)->total_bytes_received);
-}
-
-}  // namespace network
diff --git a/services/network/public/cpp/simple_url_loader_unittest.cc b/services/network/public/cpp/simple_url_loader_unittest.cc
index 2ee17dfa..0ecbbd7 100644
--- a/services/network/public/cpp/simple_url_loader_unittest.cc
+++ b/services/network/public/cpp/simple_url_loader_unittest.cc
@@ -57,7 +57,6 @@
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "services/network/test/fake_test_cert_verifier_params_factory.h"
 #include "services/network/test/test_network_context_client.h"
-#include "services/network/test/test_network_service_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
@@ -625,13 +624,13 @@
         network_context_.BindNewPipeAndPassReceiver(),
         std::move(context_params));
 
-    mojo::PendingRemote<network::mojom::NetworkServiceClient>
-        network_service_client_remote;
-    network_service_client_ = std::make_unique<TestNetworkServiceClient>(
-        network_service_client_remote.InitWithNewPipeAndPassReceiver());
-    network_service_remote->SetClient(
-        std::move(network_service_client_remote),
-        network::mojom::NetworkServiceParams::New());
+    mojo::PendingReceiver<network::mojom::URLLoaderNetworkServiceObserver>
+        default_observer_receiver;
+    network::mojom::NetworkServiceParamsPtr network_service_params =
+        network::mojom::NetworkServiceParams::New();
+    network_service_params->default_observer =
+        default_observer_receiver.InitWithNewPipeAndPassRemote();
+    network_service_remote->SetParams(std::move(network_service_params));
 
     mojo::PendingRemote<network::mojom::NetworkContextClient>
         network_context_client_remote;
@@ -687,7 +686,6 @@
   base::test::TaskEnvironment task_environment_;
 
   std::unique_ptr<network::mojom::NetworkService> network_service_;
-  std::unique_ptr<network::mojom::NetworkServiceClient> network_service_client_;
   std::unique_ptr<network::mojom::NetworkContextClient> network_context_client_;
   mojo::Remote<network::mojom::NetworkContext> network_context_;
   mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_;
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index 3ef6177..49aefdb 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -493,23 +493,6 @@
   array<url.mojom.Origin> origins;
 };
 
-// Represents the accumulated network usage for a consumer.
-struct NetworkUsage {
-  // |process_id| is 0 for the browser process, otherwise it's the child process
-  // ID.
-  int32 process_id;
-
-  // The ID of the IPC route for the consumer, which identifies the RenderFrame
-  // or like-thing.
-  int32 routing_id;
-
-  // Raw bytes received from the network since the start/restart of the service.
-  int64 total_bytes_received;
-
-  // Raw bytes sent to the network since the start/restart of the service.
-  int64 total_bytes_sent;
-};
-
 // Represents a signed exchange report.
 // Spec: https://wicg.github.io/webpackage/loading.html#queue-report
 struct SignedExchangeReport {
diff --git a/services/network/public/mojom/network_service.mojom b/services/network/public/mojom/network_service.mojom
index 5fd5496..52ef2d0 100644
--- a/services/network/public/mojom/network_service.mojom
+++ b/services/network/public/mojom/network_service.mojom
@@ -27,6 +27,7 @@
 import "services/network/public/mojom/trust_tokens.mojom";
 import "services/network/public/mojom/url_loader.mojom";
 import "services/network/public/mojom/url_loader_factory.mojom";
+import "services/network/public/mojom/url_loader_network_service_observer.mojom";
 import "services/network/public/mojom/url_request.mojom";
 import "services/network/public/mojom/url_response_head.mojom";
 import "services/network/public/mojom/client_security_state.mojom";
@@ -36,17 +37,6 @@
 [EnableIf=is_android]
 import "mojo/public/mojom/base/application_state.mojom";
 
-// Implemented by the browser process. The network service sends messages to
-// the browser process over this interface.
-interface NetworkServiceClient {
-  // Called on every request completion to update the network traffic annotation
-  // ID, and the total bytes received and sent.
-  // |network_traffic_annotation_id_hash| represents the hash of unique tag that
-  // identifies the annotation of the request.
-  OnDataUseUpdate(int32 network_traffic_annotation_id_hash, int64 recv_bytes,
-                  int64 sent_bytes);
-};
-
 // Values for configuring HTTP authentication that can only be set once.
 struct HttpAuthStaticParams {
   // List of supported auth schemes. Unrecognized schemes are ignored.
@@ -129,6 +119,10 @@
   // A set of environment variables that should be set in the network
   // service when starting up.
   array<EnvironmentVariable> environment;
+
+  // An URLLoaderFactory can provide its own URLLoaderNetworkServiceObserver
+  // but if it does not, this default will be used.
+  pending_remote<URLLoaderNetworkServiceObserver> default_observer;
 };
 
 // Information about how logging should be configured.
@@ -145,11 +139,8 @@
 // This is a trusted interface that only the browser process should have access
 // to. It must not be sent to any untrusted process like a renderer process.
 interface NetworkService {
-  // Sets client used by all |NetworkContext|s creating by |NetworkService|.
-  // Pending requests may hang if the |client| pipe is closed before they
-  // complete.
-  SetClient(pending_remote<NetworkServiceClient> client,
-            NetworkServiceParams params);
+  // Sets the parameters and initializes the service.
+  SetParams(NetworkServiceParams params);
 
   // Reinitializes the Network Service's logging with the given settings. This
   // is needed on Chrome OS, which switches to a log file in the user's home
@@ -253,9 +244,6 @@
   GetDnsConfigChangeManager(
       pending_receiver<DnsConfigChangeManager> receiver);
 
-  // Gets the accumulated network usage since the start/restart of the service.
-  GetTotalNetworkUsages() => (array<NetworkUsage> total_network_usages);
-
   // Gets list of network interfaces.
   // The |policy| parameter is a flag that specifies whether to include/exclude
   // network interfaces. Corresponds to enum net::HostAddressSelectionPolicy.
diff --git a/services/network/public/mojom/url_loader_network_service_observer.mojom b/services/network/public/mojom/url_loader_network_service_observer.mojom
index 421d681..a317c7e 100644
--- a/services/network/public/mojom/url_loader_network_service_observer.mojom
+++ b/services/network/public/mojom/url_loader_network_service_observer.mojom
@@ -139,6 +139,13 @@
   // receive the next update.
   OnLoadingStateUpdate(LoadInfo infos) => ();
 
+  // Called on every request completion to update the network traffic annotation
+  // ID, and the total bytes received and sent.
+  // |network_traffic_annotation_id_hash| represents the hash of unique tag that
+  // identifies the annotation of the request.
+  OnDataUseUpdate(int32 network_traffic_annotation_id_hash, int64 recv_bytes,
+                  int64 sent_bytes);
+
   // Used by the NetworkService to create a copy of this observer.
   // (e.g. when creating an observer for URLLoader from URLLoaderFactory's
   // observer).
diff --git a/services/network/restricted_cookie_manager.cc b/services/network/restricted_cookie_manager.cc
index 1515a8af..ffb0917 100644
--- a/services/network/restricted_cookie_manager.cc
+++ b/services/network/restricted_cookie_manager.cc
@@ -238,8 +238,6 @@
       cookie_store_(cookie_store),
       cookie_settings_(cookie_settings),
       origin_(origin),
-      site_for_cookies_(isolation_info.site_for_cookies()),
-      top_frame_origin_(isolation_info.top_frame_origin().value()),
       isolation_info_(isolation_info),
       cookie_observer_(std::move(cookie_observer)) {
   DCHECK(cookie_store);
@@ -586,15 +584,16 @@
     return false;
   }
 
-  bool site_for_cookies_ok = site_for_cookies_.IsEquivalent(site_for_cookies);
+  bool site_for_cookies_ok =
+      BoundSiteForCookies().IsEquivalent(site_for_cookies);
   DCHECK(site_for_cookies_ok)
       << "site_for_cookies from renderer='" << site_for_cookies.ToDebugString()
-      << "' from browser='" << site_for_cookies_.ToDebugString() << "';";
+      << "' from browser='" << BoundSiteForCookies().ToDebugString() << "';";
 
-  bool top_frame_origin_ok = (top_frame_origin == top_frame_origin_);
+  bool top_frame_origin_ok = (top_frame_origin == BoundTopFrameOrigin());
   DCHECK(top_frame_origin_ok)
       << "top_frame_origin from renderer='" << top_frame_origin
-      << "' from browser='" << top_frame_origin_ << "';";
+      << "' from browser='" << BoundTopFrameOrigin() << "';";
 
   UMA_HISTOGRAM_BOOLEAN("Net.RestrictedCookieManager.SiteForCookiesOK",
                         site_for_cookies_ok);
diff --git a/services/network/restricted_cookie_manager.h b/services/network/restricted_cookie_manager.h
index 6377217..edad3ff3 100644
--- a/services/network/restricted_cookie_manager.h
+++ b/services/network/restricted_cookie_manager.h
@@ -61,21 +61,11 @@
 
   ~RestrictedCookieManager() override;
 
-  void OverrideSiteForCookiesForTesting(
-      const net::SiteForCookies& new_site_for_cookies) {
-    site_for_cookies_ = new_site_for_cookies;
-  }
   void OverrideOriginForTesting(const url::Origin& new_origin) {
     origin_ = new_origin;
   }
-  void OverrideTopFrameOriginForTesting(
-      const url::Origin& new_top_frame_origin) {
-    top_frame_origin_ = new_top_frame_origin;
-  }
   void OverrideIsolationInfoForTesting(
       const net::IsolationInfo& new_isolation_info) {
-    site_for_cookies_ = new_isolation_info.site_for_cookies();
-    top_frame_origin_ = new_isolation_info.top_frame_origin().value();
     isolation_info_ = new_isolation_info;
   }
 
@@ -158,17 +148,25 @@
       const url::Origin& top_frame_origin,
       const net::CanonicalCookie* cookie_being_set = nullptr);
 
+  const net::SiteForCookies& BoundSiteForCookies() const {
+    return isolation_info_.site_for_cookies();
+  }
+
+  const url::Origin& BoundTopFrameOrigin() const {
+    return isolation_info_.top_frame_origin().value();
+  }
+
   const mojom::RestrictedCookieManagerRole role_;
   net::CookieStore* const cookie_store_;
   const CookieSettings* const cookie_settings_;
 
-  // TODO(https://crbug/1166215): Consolidate these three fields since
-  // `isolation_info_` holds copy of those values.
   url::Origin origin_;
-  net::SiteForCookies site_for_cookies_;
-  url::Origin top_frame_origin_;
 
+  // Holds the browser-provided site_for_cookies and top_frame_origin to which
+  // this RestrictedCookieManager is bound. (The frame_origin field is not used
+  // directly, but must match the `origin_` if the RCM role is SCRIPT.)
   net::IsolationInfo isolation_info_;
+
   mojo::Remote<mojom::CookieAccessObserver> cookie_observer_;
 
   base::LinkedList<Listener> listeners_;
diff --git a/services/network/restricted_cookie_manager_unittest.cc b/services/network/restricted_cookie_manager_unittest.cc
index be3a863a..5ac9f46 100644
--- a/services/network/restricted_cookie_manager_unittest.cc
+++ b/services/network/restricted_cookie_manager_unittest.cc
@@ -114,14 +114,13 @@
   // Wraps GetAllForUrl() but discards CookieAccessResult from returned cookies.
   std::vector<net::CanonicalCookie> GetAllForUrl(
       const GURL& url,
-      const GURL& site_for_cookies,
+      const net::SiteForCookies& site_for_cookies,
       const url::Origin& top_frame_origin,
       mojom::CookieManagerGetOptionsPtr options) {
     base::RunLoop run_loop;
     std::vector<net::CanonicalCookie> result;
     cookie_service_->GetAllForUrl(
-        url, net::SiteForCookies::FromUrl(site_for_cookies), top_frame_origin,
-        std::move(options),
+        url, site_for_cookies, top_frame_origin, std::move(options),
         base::BindLambdaForTesting(
             [&run_loop, &result](const std::vector<net::CookieWithAccessResult>&
                                      backend_result) {
@@ -132,37 +131,14 @@
     return result;
   }
 
-  // Returns full CookieWithAccessResult from the backend.
-  // TODO(chlily): Convert calls to the above method to this one.
-  std::vector<net::CookieWithAccessResult> GetAllForUrlWithAccessResult(
-      const GURL& url,
-      const GURL& site_for_cookies,
-      const url::Origin& top_frame_origin,
-      mojom::CookieManagerGetOptionsPtr options) {
-    base::RunLoop run_loop;
-    std::vector<net::CookieWithAccessResult> result;
-    cookie_service_->GetAllForUrl(
-        url, net::SiteForCookies::FromUrl(site_for_cookies), top_frame_origin,
-        std::move(options),
-        base::BindLambdaForTesting(
-            [&run_loop, &result](const std::vector<net::CookieWithAccessResult>&
-                                     backend_result) {
-              result = backend_result;
-              run_loop.Quit();
-            }));
-    run_loop.Run();
-    return result;
-  }
-
   bool SetCanonicalCookie(const net::CanonicalCookie& cookie,
                           const GURL& url,
-                          const GURL& site_for_cookies,
+                          const net::SiteForCookies& site_for_cookies,
                           const url::Origin& top_frame_origin) {
     base::RunLoop run_loop;
     bool result = false;
     cookie_service_->SetCanonicalCookie(
-        cookie, url, net::SiteForCookies::FromUrl(site_for_cookies),
-        top_frame_origin,
+        cookie, url, site_for_cookies, top_frame_origin,
         base::BindLambdaForTesting([&run_loop, &result](bool backend_result) {
           result = backend_result;
           run_loop.Quit();
@@ -184,13 +160,13 @@
 
   void AddChangeListener(
       const GURL& url,
-      const GURL& site_for_cookies,
+      const net::SiteForCookies& site_for_cookies,
       const url::Origin& top_frame_origin,
       mojo::PendingRemote<mojom::CookieChangeListener> listener) {
     base::RunLoop run_loop;
-    cookie_service_->AddChangeListener(
-        url, net::SiteForCookies::FromUrl(site_for_cookies), top_frame_origin,
-        std::move(listener), run_loop.QuitClosure());
+    cookie_service_->AddChangeListener(url, site_for_cookies, top_frame_origin,
+                                       std::move(listener),
+                                       run_loop.QuitClosure());
     run_loop.Run();
   }
 
@@ -205,13 +181,12 @@
  public:
   RestrictedCookieManagerTest()
       : cookie_monster_(nullptr, nullptr /* netlog */),
-        isolation_info_(net::IsolationInfo::CreateForInternalRequest(
-            url::Origin::Create(GURL("https://example.com")))),
+        isolation_info_(kDefaultIsolationInfo),
         service_(std::make_unique<RestrictedCookieManager>(
             GetParam(),
             &cookie_monster_,
             &cookie_settings_,
-            url::Origin::Create(GURL("https://example.com")),
+            kDefaultOrigin,
             isolation_info_,
             recording_client_.GetRemote())),
         receiver_(service_.get(),
@@ -307,6 +282,25 @@
     return recording_client_.recorded_activity();
   }
 
+  const GURL kDefaultUrl{"https://example.com/"};
+  const GURL kDefaultUrlWithPath{"https://example.com/test/"};
+  const GURL kOtherUrl{"https://notexample.com/"};
+  const GURL kOtherUrlWithPath{"https://notexample.com/test/"};
+  const url::Origin kDefaultOrigin = url::Origin::Create(kDefaultUrl);
+  const url::Origin kOtherOrigin = url::Origin::Create(kOtherUrl);
+  const net::SiteForCookies kDefaultSiteForCookies =
+      net::SiteForCookies::FromUrl(kDefaultUrl);
+  const net::SiteForCookies kOtherSiteForCookies =
+      net::SiteForCookies::FromUrl(kOtherUrl);
+  const net::IsolationInfo kDefaultIsolationInfo =
+      net::IsolationInfo::CreateForInternalRequest(kDefaultOrigin);
+  // IsolationInfo that replaces the default SiteForCookies with a blank one.
+  const net::IsolationInfo kOtherIsolationInfo =
+      net::IsolationInfo::Create(net::IsolationInfo::RequestType::kOther,
+                                 kDefaultOrigin,
+                                 kDefaultOrigin,
+                                 net::SiteForCookies());
+
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   net::CookieMonster cookie_monster_;
@@ -431,21 +425,17 @@
 // Test the case when `origin` differs from `isolation_info.frame_origin`.
 // RestrictedCookieManager only works for the bound origin and doesn't care
 // about the IsolationInfo's frame_origin. Technically this should only happen
-// when role == mojom::RestrictedCookieManagerRole::NETWORK.
+// when role == mojom::RestrictedCookieManagerRole::NETWORK. Otherwise, it
+// should trigger a CHECK in the RestrictedCookieManager constructor (which is
+// bypassed here due to constructing it properly, then using an origin
+// override).
 TEST_P(RestrictedCookieManagerTest,
        GetAllForUrlFromMismatchingIsolationInfoFrameOrigin) {
-  GURL top_frame_url("https://example.com");
-  GURL resource_url("https://resource.com");
-  auto top_frame_origin = url::Origin::Create(top_frame_url);
-  auto resource_origin = url::Origin::Create(resource_url);
-
-  service_->OverrideOriginForTesting(resource_origin);
+  service_->OverrideOriginForTesting(kOtherOrigin);
   // Override isolation_info to make it explicit that its frame_origin is
   // different from the origin.
-  service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
-      net::IsolationInfo::RequestType::kOther, top_frame_origin,
-      top_frame_origin, net::SiteForCookies::FromOrigin(top_frame_origin)));
-  SetSessionCookie("new-name", "new-value", resource_url.host().c_str(), "/");
+  service_->OverrideIsolationInfoForTesting(kDefaultIsolationInfo);
+  SetSessionCookie("new-name", "new-value", kOtherUrl.host().c_str(), "/");
 
   // Fetch cookies from the wrong origin (IsolationInfo's frame_origin) should
   // result in a bad message.
@@ -454,8 +444,9 @@
     options->name = "new-name";
     options->match_type = mojom::CookieMatchType::EQUALS;
     ExpectBadMessage();
-    std::vector<net::CanonicalCookie> cookies = sync_service_->GetAllForUrl(
-        top_frame_url, top_frame_url, top_frame_origin, std::move(options));
+    std::vector<net::CanonicalCookie> cookies =
+        sync_service_->GetAllForUrl(kDefaultUrl, kDefaultSiteForCookies,
+                                    kDefaultOrigin, std::move(options));
     EXPECT_TRUE(received_bad_message());
   }
   // Fetch cookies from the correct origin value which RestrictedCookieManager
@@ -465,7 +456,7 @@
     options->name = "new-name";
     options->match_type = mojom::CookieMatchType::EQUALS;
     std::vector<net::CanonicalCookie> cookies = sync_service_->GetAllForUrl(
-        resource_url, top_frame_url, top_frame_origin, std::move(options));
+        kOtherUrl, kDefaultSiteForCookies, kDefaultOrigin, std::move(options));
 
     ASSERT_THAT(cookies, testing::SizeIs(1));
 
@@ -484,19 +475,17 @@
   options->name = "";
   options->match_type = mojom::CookieMatchType::STARTS_WITH;
   EXPECT_THAT(
-      sync_service_->GetAllForUrl(
-          GURL("https://example.com/test/"), GURL("https://example.com"),
-          url::Origin::Create(GURL("https://example.com")), std::move(options)),
+      sync_service_->GetAllForUrl(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                  kDefaultOrigin, std::move(options)),
       UnorderedElementsAre(
           MatchesCookieNameValue("cookie-name", "cookie-value"),
           MatchesCookieNameValue("cookie-name-2", "cookie-value-2")));
 
   // Can also use the document.cookie-style API to get the same info.
   std::string cookies_out;
-  EXPECT_TRUE(backend()->GetCookiesString(
-      GURL("https://example.com/test/"),
-      net::SiteForCookies::FromUrl(GURL("https://example.com")),
-      url::Origin::Create(GURL("https://example.com")), &cookies_out));
+  EXPECT_TRUE(backend()->GetCookiesString(kDefaultUrlWithPath,
+                                          kDefaultSiteForCookies,
+                                          kDefaultOrigin, &cookies_out));
   EXPECT_EQ("cookie-name=cookie-value; cookie-name-2=cookie-value-2",
             cookies_out);
 }
@@ -508,9 +497,8 @@
   options->name = "";
   options->match_type = mojom::CookieMatchType::EQUALS;
   EXPECT_THAT(
-      sync_service_->GetAllForUrl(
-          GURL("https://example.com/test/"), GURL("https://example.com"),
-          url::Origin::Create(GURL("https://example.com")), std::move(options)),
+      sync_service_->GetAllForUrl(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                  kDefaultOrigin, std::move(options)),
       IsEmpty());
 }
 
@@ -522,9 +510,8 @@
   options->name = "cookie-name";
   options->match_type = mojom::CookieMatchType::EQUALS;
   EXPECT_THAT(
-      sync_service_->GetAllForUrl(
-          GURL("https://example.com/test/"), GURL("https://example.com"),
-          url::Origin::Create(GURL("https://example.com")), std::move(options)),
+      sync_service_->GetAllForUrl(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                  kDefaultOrigin, std::move(options)),
       ElementsAre(MatchesCookieNameValue("cookie-name", "cookie-value")));
 }
 
@@ -538,9 +525,8 @@
   options->name = "cookie-name-2";
   options->match_type = mojom::CookieMatchType::STARTS_WITH;
   EXPECT_THAT(
-      sync_service_->GetAllForUrl(
-          GURL("https://example.com/test/"), GURL("https://example.com"),
-          url::Origin::Create(GURL("https://example.com")), std::move(options)),
+      sync_service_->GetAllForUrl(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                  kDefaultOrigin, std::move(options)),
       UnorderedElementsAre(
           MatchesCookieNameValue("cookie-name-2", "cookie-value-2"),
           MatchesCookieNameValue("cookie-name-2b", "cookie-value-2b")));
@@ -554,9 +540,9 @@
   auto options = mojom::CookieManagerGetOptions::New();
   options->name = "cookie-name";
   options->match_type = mojom::CookieMatchType::STARTS_WITH;
-  std::vector<net::CanonicalCookie> cookies = sync_service_->GetAllForUrl(
-      GURL("https://example.com/test/"), GURL("https://example.com"),
-      url::Origin::Create(GURL("https://example.com")), std::move(options));
+  std::vector<net::CanonicalCookie> cookies =
+      sync_service_->GetAllForUrl(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                  kDefaultOrigin, std::move(options));
 
   if (GetParam() == mojom::RestrictedCookieManagerRole::SCRIPT) {
     EXPECT_THAT(cookies, UnorderedElementsAre(MatchesCookieNameValue(
@@ -573,7 +559,7 @@
 TEST_P(RestrictedCookieManagerTest, GetAllForUrlFromWrongOrigin) {
   SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
   SetSessionCookie("cookie-name-2", "cookie-value-2", "example.com", "/");
-  SetSessionCookie("other-cookie-name", "other-cookie-value", "not-example.com",
+  SetSessionCookie("other-cookie-name", "other-cookie-value", "notexample.com",
                    "/");
 
   auto options = mojom::CookieManagerGetOptions::New();
@@ -581,9 +567,8 @@
   options->match_type = mojom::CookieMatchType::STARTS_WITH;
   ExpectBadMessage();
   EXPECT_THAT(
-      sync_service_->GetAllForUrl(
-          GURL("https://not-example.com/test/"), GURL("https://example.com"),
-          url::Origin::Create(GURL("https://example.com")), std::move(options)),
+      sync_service_->GetAllForUrl(kOtherUrlWithPath, kDefaultSiteForCookies,
+                                  kDefaultOrigin, std::move(options)),
       IsEmpty());
   EXPECT_TRUE(received_bad_message());
 }
@@ -600,9 +585,8 @@
   options->match_type = mojom::CookieMatchType::STARTS_WITH;
   ExpectBadMessage();
   EXPECT_THAT(
-      sync_service_->GetAllForUrl(
-          GURL("https://example.com/test/"), GURL("https://example.com"),
-          url::Origin::Create(GURL("https://example.com")), std::move(options)),
+      sync_service_->GetAllForUrl(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                  kDefaultOrigin, std::move(options)),
       IsEmpty());
   EXPECT_TRUE(received_bad_message());
 }
@@ -610,22 +594,19 @@
 TEST_P(RestrictedCookieManagerTest, GetCookieStringFromWrongOrigin) {
   SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
   SetSessionCookie("cookie-name-2", "cookie-value-2", "example.com", "/");
-  SetSessionCookie("other-cookie-name", "other-cookie-value", "not-example.com",
+  SetSessionCookie("other-cookie-name", "other-cookie-value", "notexample.com",
                    "/");
 
   ExpectBadMessage();
   std::string cookies_out;
   EXPECT_TRUE(backend()->GetCookiesString(
-      GURL("https://notexample.com/test/"),
-      net::SiteForCookies::FromUrl(GURL("https://example.com")),
-      url::Origin::Create(GURL("https://example.com")), &cookies_out));
+      kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin, &cookies_out));
   EXPECT_TRUE(received_bad_message());
   EXPECT_THAT(cookies_out, IsEmpty());
 }
 
 TEST_P(RestrictedCookieManagerTest, GetAllForUrlPolicy) {
-  service_->OverrideSiteForCookiesForTesting(
-      net::SiteForCookies::FromUrl(GURL("https://notexample.com")));
+  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
   SetSessionCookie("cookie-name", "cookie-value", "example.com", "/");
 
   // With default policy, should be able to get all cookies, even third-party.
@@ -635,17 +616,15 @@
     options->match_type = mojom::CookieMatchType::STARTS_WITH;
 
     EXPECT_THAT(
-        sync_service_->GetAllForUrl(
-            GURL("https://example.com/test/"), GURL("https://notexample.com"),
-            url::Origin::Create(GURL("https://example.com")),
-            std::move(options)),
+        sync_service_->GetAllForUrl(kDefaultUrlWithPath, net::SiteForCookies(),
+                                    kDefaultOrigin, std::move(options)),
         ElementsAre(MatchesCookieNameValue("cookie-name", "cookie-value")));
   }
 
   EXPECT_THAT(recorded_activity(),
               ElementsAre(MatchesCookieOp(
                   mojom::CookieAccessDetails::Type::kRead,
-                  "https://example.com/test/", "https://notexample.com/",
+                  "https://example.com/test/", "",
                   CookieOrLine("cookie-name=cookie-value",
                                mojom::CookieOrLine::Tag::COOKIE),
                   testing::AllOf(IsInclude(), Not(ShouldWarn())))));
@@ -658,10 +637,8 @@
     options->match_type = mojom::CookieMatchType::STARTS_WITH;
 
     EXPECT_THAT(
-        sync_service_->GetAllForUrl(
-            GURL("https://example.com/test/"), GURL("https://notexample.com"),
-            url::Origin::Create(GURL("https://example.com")),
-            std::move(options)),
+        sync_service_->GetAllForUrl(kDefaultUrlWithPath, net::SiteForCookies(),
+                                    kDefaultOrigin, std::move(options)),
         IsEmpty());
   }
 
@@ -671,7 +648,7 @@
           testing::_,
           MatchesCookieOp(
               mojom::CookieAccessDetails::Type::kRead,
-              "https://example.com/test/", "https://notexample.com/",
+              "https://example.com/test/", "",
               CookieOrLine("cookie-name=cookie-value",
                            mojom::CookieOrLine::Tag::COOKIE),
               HasExactlyExclusionReasonsForTesting(
@@ -680,8 +657,7 @@
 }
 
 TEST_P(RestrictedCookieManagerTest, GetAllForUrlPolicyWarnActual) {
-  service_->OverrideSiteForCookiesForTesting(
-      net::SiteForCookies::FromUrl(GURL("https://notexample.com")));
+  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
   {
     // Disable kCookiesWithoutSameSiteMustBeSecure to inject such a cookie.
     base::test::ScopedFeatureList feature_list;
@@ -708,17 +684,15 @@
     options->match_type = mojom::CookieMatchType::STARTS_WITH;
 
     EXPECT_THAT(
-        sync_service_->GetAllForUrl(
-            GURL("https://example.com/test/"), GURL("https://notexample.com"),
-            url::Origin::Create(GURL("https://example.com")),
-            std::move(options)),
+        sync_service_->GetAllForUrl(kDefaultUrlWithPath, net::SiteForCookies(),
+                                    kDefaultOrigin, std::move(options)),
         IsEmpty());
   }
 
   EXPECT_THAT(recorded_activity(),
               ElementsAre(MatchesCookieOp(
                   mojom::CookieAccessDetails::Type::kRead,
-                  "https://example.com/test/", "https://notexample.com/",
+                  "https://example.com/test/", "",
                   CookieOrLine("cookie-name=cookie-value",
                                mojom::CookieOrLine::Tag::COOKIE),
                   HasExactlyExclusionReasonsForTesting(
@@ -736,18 +710,15 @@
     options->match_type = mojom::CookieMatchType::STARTS_WITH;
 
     EXPECT_THAT(
-        sync_service_->GetAllForUrl(
-            GURL("https://example.com/test/"), GURL("https://example.com"),
-            url::Origin::Create(GURL("https://example.com")),
-            std::move(options)),
+        sync_service_->GetAllForUrl(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                    kDefaultOrigin, std::move(options)),
         ElementsAre(MatchesCookieNameValue("cookie-name", "cookie-value")));
   }
   // Same Party. `party_context` contains fps site.
   {
     service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
-        net::IsolationInfo::RequestType::kOther,
-        url::Origin::Create(GURL("https://example.com")),
-        url::Origin::Create(GURL("https://example.com")), net::SiteForCookies(),
+        net::IsolationInfo::RequestType::kOther, kDefaultOrigin, kDefaultOrigin,
+        net::SiteForCookies(),
         std::set<net::SchemefulSite>{
             net::SchemefulSite(GURL("https://member1.com"))}));
 
@@ -756,10 +727,8 @@
     options->match_type = mojom::CookieMatchType::STARTS_WITH;
 
     EXPECT_THAT(
-        sync_service_->GetAllForUrl(
-            GURL("https://example.com/test/"), GURL(),
-            url::Origin::Create(GURL("https://example.com")),
-            std::move(options)),
+        sync_service_->GetAllForUrl(kDefaultUrlWithPath, net::SiteForCookies(),
+                                    kDefaultOrigin, std::move(options)),
         ElementsAre(MatchesCookieNameValue("cookie-name", "cookie-value")));
   }
 
@@ -767,9 +736,8 @@
   {
     // `party_context` contains cross-party site.
     service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
-        net::IsolationInfo::RequestType::kOther,
-        url::Origin::Create(GURL("https://example.com")),
-        url::Origin::Create(GURL("https://example.com")), net::SiteForCookies(),
+        net::IsolationInfo::RequestType::kOther, kDefaultOrigin, kDefaultOrigin,
+        net::SiteForCookies(),
         std::set<net::SchemefulSite>{
             net::SchemefulSite(GURL("https://nonexample.com"))}));
 
@@ -777,11 +745,10 @@
     options->name = "cookie-name";
     options->match_type = mojom::CookieMatchType::STARTS_WITH;
 
-    EXPECT_THAT(sync_service_->GetAllForUrl(
-                    GURL("https://example.com/test/"), GURL(),
-                    url::Origin::Create(GURL("https://example.com")),
-                    std::move(options)),
-                IsEmpty());
+    EXPECT_THAT(
+        sync_service_->GetAllForUrl(kDefaultUrlWithPath, net::SiteForCookies(),
+                                    kDefaultOrigin, std::move(options)),
+        IsEmpty());
 
     EXPECT_THAT(
         recorded_activity(),
@@ -805,16 +772,14 @@
           base::Time(), base::Time(), /* secure = */ true,
           /* httponly = */ false, net::CookieSameSite::NO_RESTRICTION,
           net::COOKIE_PRIORITY_DEFAULT, /* same_party = */ false),
-      GURL("https://example.com/test/"), GURL("https://example.com"),
-      url::Origin::Create(GURL("https://example.com"))));
+      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin));
 
   auto options = mojom::CookieManagerGetOptions::New();
   options->name = "new-name";
   options->match_type = mojom::CookieMatchType::EQUALS;
   EXPECT_THAT(
-      sync_service_->GetAllForUrl(
-          GURL("https://example.com/test/"), GURL("https://example.com"),
-          url::Origin::Create(GURL("https://example.com")), std::move(options)),
+      sync_service_->GetAllForUrl(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                  kDefaultOrigin, std::move(options)),
       ElementsAre(MatchesCookieNameValue("new-name", "new-value")));
 }
 
@@ -826,15 +791,14 @@
                     base::Time(), base::Time(), /* secure = */ true,
                     /* httponly = */ true, net::CookieSameSite::NO_RESTRICTION,
                     net::COOKIE_PRIORITY_DEFAULT, /* same_party = */ false),
-                GURL("https://example.com/test/"), GURL("https://example.com"),
-                url::Origin::Create(GURL("https://example.com"))));
+                kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin));
 
   auto options = mojom::CookieManagerGetOptions::New();
   options->name = "new-name";
   options->match_type = mojom::CookieMatchType::EQUALS;
-  std::vector<net::CanonicalCookie> cookies = sync_service_->GetAllForUrl(
-      GURL("https://example.com/test/"), GURL("https://example.com"),
-      url::Origin::Create(GURL("https://example.com")), std::move(options));
+  std::vector<net::CanonicalCookie> cookies =
+      sync_service_->GetAllForUrl(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                  kDefaultOrigin, std::move(options));
 
   if (GetParam() == mojom::RestrictedCookieManagerRole::SCRIPT) {
     EXPECT_THAT(cookies, IsEmpty());
@@ -846,17 +810,14 @@
 
 TEST_P(RestrictedCookieManagerTest, SetCookieFromString) {
   EXPECT_TRUE(backend()->SetCookieFromString(
-      GURL("https://example.com/test/"),
-      net::SiteForCookies::FromUrl(GURL("https://example.com")),
-      url::Origin::Create(GURL("https://example.com")),
+      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
       "new-name=new-value;path=/"));
   auto options = mojom::CookieManagerGetOptions::New();
   options->name = "new-name";
   options->match_type = mojom::CookieMatchType::EQUALS;
   EXPECT_THAT(
-      sync_service_->GetAllForUrl(
-          GURL("https://example.com/test/"), GURL("https://example.com"),
-          url::Origin::Create(GURL("https://example.com")), std::move(options)),
+      sync_service_->GetAllForUrl(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                  kDefaultOrigin, std::move(options)),
       ElementsAre(MatchesCookieNameValue("new-name", "new-value")));
 }
 
@@ -868,8 +829,7 @@
           base::Time(), base::Time(), /* secure = */ true,
           /* httponly = */ false, net::CookieSameSite::NO_RESTRICTION,
           net::COOKIE_PRIORITY_DEFAULT, /* same_party = */ false),
-      GURL("https://not-example.com/test/"), GURL("https://example.com"),
-      url::Origin::Create(GURL("https://example.com"))));
+      kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin));
   ASSERT_TRUE(received_bad_message());
 }
 
@@ -885,8 +845,7 @@
           base::Time(), base::Time(), /* secure = */ true,
           /* httponly = */ false, net::CookieSameSite::NO_RESTRICTION,
           net::COOKIE_PRIORITY_DEFAULT, /* same_party = */ false),
-      GURL("https://example.com/test/"), GURL("https://example.com"),
-      url::Origin::Create(GURL("https://example.com"))));
+      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin));
   ASSERT_TRUE(received_bad_message());
 }
 
@@ -898,52 +857,44 @@
           base::Time(), base::Time(), /* secure = */ true,
           /* httponly = */ false, net::CookieSameSite::NO_RESTRICTION,
           net::COOKIE_PRIORITY_DEFAULT, /* same_party = */ false),
-      GURL("https://example.com/test/"), GURL("https://example.com"),
-      url::Origin::Create(GURL("https://example.com"))));
+      kDefaultUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin));
   ASSERT_TRUE(received_bad_message());
 }
 
 TEST_P(RestrictedCookieManagerTest, SetCookieFromStringWrongOrigin) {
   ExpectBadMessage();
   EXPECT_TRUE(backend()->SetCookieFromString(
-      GURL("https://notexample.com/test/"),
-      net::SiteForCookies::FromUrl(GURL("https://example.com")),
-      url::Origin::Create(GURL("https://example.com")),
+      kOtherUrlWithPath, kDefaultSiteForCookies, kDefaultOrigin,
       "new-name=new-value;path=/"));
   ASSERT_TRUE(received_bad_message());
 }
 
 TEST_P(RestrictedCookieManagerTest, SetCanonicalCookiePolicy) {
-  service_->OverrideSiteForCookiesForTesting(
-      net::SiteForCookies::FromUrl(GURL("https://notexample.com")));
+  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
   {
     // With default settings object, setting a third-party cookie is OK.
     auto cookie = net::CanonicalCookie::Create(
-        GURL("https://example.com"), "A=B; SameSite=none; Secure",
-        base::Time::Now(), base::nullopt /* server_time */);
+        kDefaultUrl, "A=B; SameSite=none; Secure", base::Time::Now(),
+        base::nullopt /* server_time */);
     EXPECT_TRUE(sync_service_->SetCanonicalCookie(
-        *cookie, GURL("https://example.com"), GURL("https://notexample.com"),
-        url::Origin::Create(GURL("https://example.com"))));
+        *cookie, kDefaultUrl, net::SiteForCookies(), kDefaultOrigin));
   }
 
-  EXPECT_THAT(recorded_activity(),
-              ElementsAre(MatchesCookieOp(
-                  mojom::CookieAccessDetails::Type::kChange,
-                  "https://example.com/", "https://notexample.com/",
-                  CookieOrLine("A=B", mojom::CookieOrLine::Tag::COOKIE),
-                  testing::AllOf(IsInclude(), Not(ShouldWarn())))));
+  EXPECT_THAT(
+      recorded_activity(),
+      ElementsAre(MatchesCookieOp(
+          mojom::CookieAccessDetails::Type::kChange, "https://example.com/", "",
+          CookieOrLine("A=B", mojom::CookieOrLine::Tag::COOKIE),
+          testing::AllOf(IsInclude(), Not(ShouldWarn())))));
 
-  service_->OverrideSiteForCookiesForTesting(
-      net::SiteForCookies::FromUrl(GURL("https://otherexample.com")));
   {
     // Not if third-party cookies are disabled, though.
     cookie_settings_.set_block_third_party_cookies(true);
     auto cookie = net::CanonicalCookie::Create(
-        GURL("https://example.com"), "A2=B2; SameSite=none; Secure",
-        base::Time::Now(), base::nullopt /* server_time */);
+        kDefaultUrl, "A2=B2; SameSite=none; Secure", base::Time::Now(),
+        base::nullopt /* server_time */);
     EXPECT_FALSE(sync_service_->SetCanonicalCookie(
-        *cookie, GURL("https://example.com"), GURL("https://otherexample.com"),
-        url::Origin::Create(GURL("https://example.com"))));
+        *cookie, kDefaultUrl, net::SiteForCookies(), kDefaultOrigin));
   }
 
   EXPECT_THAT(
@@ -952,8 +903,7 @@
           testing::_,
           MatchesCookieOp(
               mojom::CookieAccessDetails::Type::kChange, "https://example.com/",
-              "https://otherexample.com/",
-              CookieOrLine("A2=B2", mojom::CookieOrLine::Tag::COOKIE),
+              "", CookieOrLine("A2=B2", mojom::CookieOrLine::Tag::COOKIE),
               HasExactlyExclusionReasonsForTesting(
                   std::vector<net::CookieInclusionStatus::ExclusionReason>{
                       net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES}))));
@@ -963,12 +913,10 @@
   options->name = "A";
   options->match_type = mojom::CookieMatchType::STARTS_WITH;
 
-  service_->OverrideSiteForCookiesForTesting(
-      net::SiteForCookies::FromUrl(GURL("https://example.com")));
+  service_->OverrideIsolationInfoForTesting(kDefaultIsolationInfo);
   EXPECT_THAT(
-      sync_service_->GetAllForUrl(
-          GURL("https://example.com/test/"), GURL("https://example.com/"),
-          url::Origin::Create(GURL("https://example.com")), std::move(options)),
+      sync_service_->GetAllForUrl(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                  kDefaultOrigin, std::move(options)),
       ElementsAre(MatchesCookieNameValue("A", "B")));
 
   EXPECT_THAT(
@@ -982,43 +930,39 @@
 }
 
 TEST_P(RestrictedCookieManagerTest, SetCanonicalCookiePolicyWarnActual) {
-  service_->OverrideSiteForCookiesForTesting(
-      net::SiteForCookies::FromUrl(GURL("https://notexample.com")));
+  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
   // Make sure the deprecation warnings are also produced when the feature
   // to enable the new behavior is on.
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(net::features::kSameSiteByDefaultCookies);
 
-  auto cookie = net::CanonicalCookie::Create(GURL("https://example.com"), "A=B",
-                                             base::Time::Now(),
-                                             base::nullopt /* server_time */);
+  auto cookie = net::CanonicalCookie::Create(
+      kDefaultUrl, "A=B", base::Time::Now(), base::nullopt /* server_time */);
   EXPECT_FALSE(sync_service_->SetCanonicalCookie(
-      *cookie, GURL("https://example.com"), GURL("https://notexample.com"),
-      url::Origin::Create(GURL("https://example.com"))));
+      *cookie, kDefaultUrl, net::SiteForCookies(), kDefaultOrigin));
 
-  EXPECT_THAT(recorded_activity(),
-              ElementsAre(MatchesCookieOp(
-                  mojom::CookieAccessDetails::Type::kChange,
-                  "https://example.com/", "https://notexample.com/",
-                  CookieOrLine("A=B", mojom::CookieOrLine::Tag::COOKIE),
-                  HasExactlyExclusionReasonsForTesting(
-                      std::vector<net::CookieInclusionStatus::ExclusionReason>{
-                          net::CookieInclusionStatus::
-                              EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX}))));
+  EXPECT_THAT(
+      recorded_activity(),
+      ElementsAre(MatchesCookieOp(
+          mojom::CookieAccessDetails::Type::kChange, "https://example.com/", "",
+          CookieOrLine("A=B", mojom::CookieOrLine::Tag::COOKIE),
+          HasExactlyExclusionReasonsForTesting(
+              std::vector<net::CookieInclusionStatus::ExclusionReason>{
+                  net::CookieInclusionStatus::
+                      EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX}))));
 }
 
 TEST_P(SamePartyEnabledRestrictedCookieManagerTest,
        SetCookieFromString_SameParty_ReportsInvalid) {
   // Invalid. Should be reported.
-  sync_service_->SetCookieFromString(
-      GURL("https://example.com/test/"), net::SiteForCookies(),
-      url::Origin::Create(GURL("https://example.com")), "name=value;SameParty");
+  sync_service_->SetCookieFromString(kDefaultUrlWithPath, net::SiteForCookies(),
+                                     kDefaultOrigin, "name=value;SameParty");
 
   EXPECT_THAT(
       recorded_activity(),
       ElementsAre(MatchesCookieOp(
-          mojom::CookieAccessDetails::Type::kChange,
-          GURL("https://example.com/test/"), GURL(),
+          mojom::CookieAccessDetails::Type::kChange, kDefaultUrlWithPath,
+          GURL(),
           CookieOrLine("name=value;SameParty",
                        mojom::CookieOrLine::Tag::COOKIE_STRING),
           HasExactlyExclusionReasonsForTesting(
@@ -1031,8 +975,7 @@
   // Same Party. `party_context` contains fps site.
   {
     service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
-        net::IsolationInfo::RequestType::kOther,
-        url::Origin::Create(GURL("https://example.com")),
+        net::IsolationInfo::RequestType::kOther, kDefaultOrigin,
         url::Origin::Create(GURL("https://member1.com")), net::SiteForCookies(),
         std::set<net::SchemefulSite>{
             net::SchemefulSite(GURL("https://member1.com"))}));
@@ -1046,31 +989,28 @@
             base::Time(), base::Time(), /* secure = */ true,
             /* httponly = */ false, net::CookieSameSite::LAX_MODE,
             net::COOKIE_PRIORITY_DEFAULT, /* same_party = */ true),
-        GURL("https://member1.com/test/"), GURL(),
-        url::Origin::Create(GURL("https://example.com"))));
+        GURL("https://member1.com/test/"), net::SiteForCookies(),
+        kDefaultOrigin));
 
     auto options = mojom::CookieManagerGetOptions::New();
     options->name = "new-name";
     options->match_type = mojom::CookieMatchType::EQUALS;
-    EXPECT_THAT(sync_service_->GetAllForUrl(
-                    GURL("https://member1.com/test/"), GURL(),
-                    url::Origin::Create(GURL("https://example.com")),
-                    std::move(options)),
+    EXPECT_THAT(sync_service_->GetAllForUrl(GURL("https://member1.com/test/"),
+                                            net::SiteForCookies(),
+                                            kDefaultOrigin, std::move(options)),
                 ElementsAre(MatchesCookieNameValue("new-name", "new-value")));
   }
 
   // Cross Party
   {
     service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
-        net::IsolationInfo::RequestType::kOther,
-        url::Origin::Create(GURL("https://example.com")),
-        url::Origin::Create(GURL("https://example.com")), net::SiteForCookies(),
+        net::IsolationInfo::RequestType::kOther, kDefaultOrigin, kDefaultOrigin,
+        net::SiteForCookies(),
         std::set<net::SchemefulSite>{
             net::SchemefulSite(GURL("https://not-example.com"))}));
     // Need to restore the origin value since the previous Same Party test case
     // changed it to member1.com.
-    service_->OverrideOriginForTesting(
-        url::Origin::Create(GURL("https://example.com")));
+    service_->OverrideOriginForTesting(kDefaultOrigin);
 
     EXPECT_FALSE(sync_service_->SetCanonicalCookie(
         *net::CanonicalCookie::CreateUnsafeCookieForTesting(
@@ -1078,8 +1018,7 @@
             base::Time(), base::Time(), /* secure = */ true,
             /* httponly = */ false, net::CookieSameSite::NO_RESTRICTION,
             net::COOKIE_PRIORITY_DEFAULT, /* same_party = */ true),
-        GURL("https://example.com/test/"), GURL(),
-        url::Origin::Create(GURL("https://example.com"))));
+        kDefaultUrlWithPath, net::SiteForCookies(), kDefaultOrigin));
 
     EXPECT_THAT(
         recorded_activity(),
@@ -1098,31 +1037,23 @@
 }
 
 TEST_P(RestrictedCookieManagerTest, CookiesEnabledFor) {
-  service_->OverrideSiteForCookiesForTesting(
-      net::SiteForCookies::FromUrl(GURL("https://notexample.com")));
+  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
   // Default, third-party access is OK.
   bool result = false;
-  EXPECT_TRUE(backend()->CookiesEnabledFor(
-      GURL("https://example.com"),
-      net::SiteForCookies::FromUrl(GURL("https://notexample.com")),
-      url::Origin::Create(GURL("https://example.com")), &result));
+  EXPECT_TRUE(backend()->CookiesEnabledFor(kDefaultUrl, net::SiteForCookies(),
+                                           kDefaultOrigin, &result));
   EXPECT_TRUE(result);
 
-  // Third-part cookies disabled.
+  // Third-party cookies disabled.
   cookie_settings_.set_block_third_party_cookies(true);
-  EXPECT_TRUE(backend()->CookiesEnabledFor(
-      GURL("https://example.com"),
-      net::SiteForCookies::FromUrl(GURL("https://notexample.com")),
-      url::Origin::Create(GURL("https://example.com")), &result));
+  EXPECT_TRUE(backend()->CookiesEnabledFor(kDefaultUrl, net::SiteForCookies(),
+                                           kDefaultOrigin, &result));
   EXPECT_FALSE(result);
 
   // First-party ones still OK.
-  service_->OverrideSiteForCookiesForTesting(
-      net::SiteForCookies::FromUrl(GURL("https://example.com")));
-  EXPECT_TRUE(backend()->CookiesEnabledFor(
-      GURL("https://example.com"),
-      net::SiteForCookies::FromUrl(GURL("https://example.com")),
-      url::Origin::Create(GURL("https://example.com")), &result));
+  service_->OverrideIsolationInfoForTesting(kDefaultIsolationInfo);
+  EXPECT_TRUE(backend()->CookiesEnabledFor(kDefaultUrl, kDefaultSiteForCookies,
+                                           kDefaultOrigin, &result));
   EXPECT_TRUE(result);
 }
 
@@ -1139,59 +1070,64 @@
   GURL https_url("https://example.com/test");
   auto http_origin = url::Origin::Create(http_url);
   auto https_origin = url::Origin::Create(https_url);
+  auto chrome_origin = url::Origin::Create(chrome_url);
+  net::SiteForCookies chrome_site_for_cookies =
+      net::SiteForCookies::FromUrl(chrome_url);
 
   // Test if site_for_cookies is chrome, then SameSite cookies can be
   // set and gotten if the origin is secure.
-  service_->OverrideSiteForCookiesForTesting(
-      net::SiteForCookies::FromUrl(chrome_url));
+  service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
+      net::IsolationInfo::RequestType::kOther, chrome_origin, https_origin,
+      chrome_site_for_cookies));
   service_->OverrideOriginForTesting(https_origin);
-  service_->OverrideTopFrameOriginForTesting(https_origin);
   EXPECT_TRUE(sync_service_->SetCanonicalCookie(
       *net::CanonicalCookie::CreateUnsafeCookieForTesting(
           "strict-cookie", "1", "example.com", "/", base::Time(), base::Time(),
           base::Time(), /* secure = */ false,
           /* httponly = */ false, net::CookieSameSite::STRICT_MODE,
           net::COOKIE_PRIORITY_DEFAULT, /* same_party = */ false),
-      https_url, chrome_url, https_origin));
+      https_url, chrome_site_for_cookies, chrome_origin));
   EXPECT_TRUE(sync_service_->SetCanonicalCookie(
       *net::CanonicalCookie::CreateUnsafeCookieForTesting(
           "lax-cookie", "1", "example.com", "/", base::Time(), base::Time(),
           base::Time(), /* secure = */ false,
           /* httponly = */ false, net::CookieSameSite::LAX_MODE,
           net::COOKIE_PRIORITY_DEFAULT, /* same_party = */ false),
-      https_url, chrome_url, https_origin));
+      https_url, chrome_site_for_cookies, chrome_origin));
 
   auto options = mojom::CookieManagerGetOptions::New();
   options->name = "";
   options->match_type = mojom::CookieMatchType::STARTS_WITH;
-  EXPECT_THAT(sync_service_->GetAllForUrl(https_url, chrome_url, https_origin,
-                                          std::move(options)),
+  EXPECT_THAT(sync_service_->GetAllForUrl(https_url, chrome_site_for_cookies,
+                                          chrome_origin, std::move(options)),
               testing::SizeIs(2));
 
   // Test if site_for_cookies is chrome, then SameSite cookies cannot be
   // set and gotten if the origin is not secure.
+  service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
+      net::IsolationInfo::RequestType::kOther, chrome_origin, http_origin,
+      chrome_site_for_cookies));
   service_->OverrideOriginForTesting(http_origin);
-  service_->OverrideTopFrameOriginForTesting(http_origin);
   EXPECT_FALSE(sync_service_->SetCanonicalCookie(
       *net::CanonicalCookie::CreateUnsafeCookieForTesting(
           "strict-cookie", "2", "example.com", "/", base::Time(), base::Time(),
           base::Time(), /* secure = */ false,
           /* httponly = */ false, net::CookieSameSite::STRICT_MODE,
           net::COOKIE_PRIORITY_DEFAULT, /* same_party = */ false),
-      http_url, chrome_url, http_origin));
+      http_url, chrome_site_for_cookies, chrome_origin));
   EXPECT_FALSE(sync_service_->SetCanonicalCookie(
       *net::CanonicalCookie::CreateUnsafeCookieForTesting(
           "lax-cookie", "2", "example.com", "/", base::Time(), base::Time(),
           base::Time(), /* secure = */ false,
           /* httponly = */ false, net::CookieSameSite::LAX_MODE,
           net::COOKIE_PRIORITY_DEFAULT, /* same_party = */ false),
-      http_url, chrome_url, http_origin));
+      http_url, chrome_site_for_cookies, chrome_origin));
 
   options = mojom::CookieManagerGetOptions::New();
   options->name = "";
   options->match_type = mojom::CookieMatchType::STARTS_WITH;
-  EXPECT_THAT(sync_service_->GetAllForUrl(http_url, chrome_url, http_origin,
-                                          std::move(options)),
+  EXPECT_THAT(sync_service_->GetAllForUrl(http_url, chrome_site_for_cookies,
+                                          chrome_origin, std::move(options)),
               IsEmpty());
 }
 
@@ -1240,10 +1176,8 @@
   mojo::PendingRemote<network::mojom::CookieChangeListener> listener_remote;
   mojo::PendingReceiver<network::mojom::CookieChangeListener> receiver =
       listener_remote.InitWithNewPipeAndPassReceiver();
-  sync_service_->AddChangeListener(
-      GURL("https://example.com/test/"), GURL("https://example.com"),
-      url::Origin::Create(GURL("https://example.com")),
-      std::move(listener_remote));
+  sync_service_->AddChangeListener(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                   kDefaultOrigin, std::move(listener_remote));
   TestCookieChangeListener listener(std::move(receiver));
 
   ASSERT_THAT(listener.observed_changes(), testing::SizeIs(0));
@@ -1259,15 +1193,12 @@
 }
 
 TEST_P(RestrictedCookieManagerTest, ChangeSettings) {
-  service_->OverrideSiteForCookiesForTesting(
-      net::SiteForCookies::FromUrl(GURL("https://notexample.com/")));
+  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
   mojo::PendingRemote<network::mojom::CookieChangeListener> listener_remote;
   mojo::PendingReceiver<network::mojom::CookieChangeListener> receiver =
       listener_remote.InitWithNewPipeAndPassReceiver();
-  sync_service_->AddChangeListener(
-      GURL("https://example.com/test/"), GURL("https://notexample.com"),
-      url::Origin::Create(GURL("https://example.com")),
-      std::move(listener_remote));
+  sync_service_->AddChangeListener(kDefaultUrlWithPath, net::SiteForCookies(),
+                                   kDefaultOrigin, std::move(listener_remote));
   TestCookieChangeListener listener(std::move(receiver));
 
   EXPECT_THAT(listener.observed_changes(), IsEmpty());
@@ -1283,10 +1214,9 @@
   mojo::PendingReceiver<network::mojom::CookieChangeListener> bad_receiver =
       bad_listener_remote.InitWithNewPipeAndPassReceiver();
   ExpectBadMessage();
-  sync_service_->AddChangeListener(
-      GURL("https://not-example.com/test/"), GURL("https://example.com"),
-      url::Origin::Create(GURL("https://example.com")),
-      std::move(bad_listener_remote));
+  sync_service_->AddChangeListener(kOtherUrlWithPath, kDefaultSiteForCookies,
+                                   kDefaultOrigin,
+                                   std::move(bad_listener_remote));
   EXPECT_TRUE(received_bad_message());
   TestCookieChangeListener bad_listener(std::move(bad_receiver));
 
@@ -1294,10 +1224,9 @@
       good_listener_remote;
   mojo::PendingReceiver<network::mojom::CookieChangeListener> good_receiver =
       good_listener_remote.InitWithNewPipeAndPassReceiver();
-  sync_service_->AddChangeListener(
-      GURL("https://example.com/test/"), GURL("https://example.com"),
-      url::Origin::Create(GURL("https://example.com")),
-      std::move(good_listener_remote));
+  sync_service_->AddChangeListener(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                   kDefaultOrigin,
+                                   std::move(good_listener_remote));
   TestCookieChangeListener good_listener(std::move(good_receiver));
 
   ASSERT_THAT(bad_listener.observed_changes(), IsEmpty());
@@ -1326,10 +1255,9 @@
   mojo::PendingReceiver<network::mojom::CookieChangeListener> bad_receiver =
       bad_listener_remote.InitWithNewPipeAndPassReceiver();
   ExpectBadMessage();
-  sync_service_->AddChangeListener(
-      GURL("https://example.com/test/"), GURL("https://example.com"),
-      url::Origin::Create(GURL("https://example.com")),
-      std::move(bad_listener_remote));
+  sync_service_->AddChangeListener(kDefaultUrlWithPath, kDefaultSiteForCookies,
+                                   kDefaultOrigin,
+                                   std::move(bad_listener_remote));
   EXPECT_TRUE(received_bad_message());
 
   TestCookieChangeListener bad_listener(std::move(bad_receiver));
@@ -1341,19 +1269,17 @@
   // Same Party. `party_context` contains fps site.
   {
     service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
-        net::IsolationInfo::RequestType::kOther,
-        url::Origin::Create(GURL("https://example.com")),
-        url::Origin::Create(GURL("https://example.com")), net::SiteForCookies(),
+        net::IsolationInfo::RequestType::kOther, kDefaultOrigin, kDefaultOrigin,
+        net::SiteForCookies(),
         std::set<net::SchemefulSite>{
             net::SchemefulSite(GURL("https://member1.com"))}));
 
     mojo::PendingRemote<network::mojom::CookieChangeListener> listener_remote;
     mojo::PendingReceiver<network::mojom::CookieChangeListener> receiver =
         listener_remote.InitWithNewPipeAndPassReceiver();
-    sync_service_->AddChangeListener(
-        GURL("https://example.com/test/"), GURL(),
-        url::Origin::Create(GURL("https://example.com")),
-        std::move(listener_remote));
+    sync_service_->AddChangeListener(kDefaultUrlWithPath, net::SiteForCookies(),
+                                     kDefaultOrigin,
+                                     std::move(listener_remote));
     TestCookieChangeListener listener(std::move(receiver));
 
     ASSERT_THAT(listener.observed_changes(), IsEmpty());
@@ -1370,19 +1296,17 @@
   // Cross Party.
   {
     service_->OverrideIsolationInfoForTesting(net::IsolationInfo::Create(
-        net::IsolationInfo::RequestType::kOther,
-        url::Origin::Create(GURL("https://example.com")),
-        url::Origin::Create(GURL("https://example.com")), net::SiteForCookies(),
+        net::IsolationInfo::RequestType::kOther, kDefaultOrigin, kDefaultOrigin,
+        net::SiteForCookies(),
         std::set<net::SchemefulSite>{
             net::SchemefulSite(GURL("https://not-example.com"))}));
 
     mojo::PendingRemote<network::mojom::CookieChangeListener> listener_remote;
     mojo::PendingReceiver<network::mojom::CookieChangeListener> receiver =
         listener_remote.InitWithNewPipeAndPassReceiver();
-    sync_service_->AddChangeListener(
-        GURL("https://example.com/test/"), GURL(),
-        url::Origin::Create(GURL("https://example.com")),
-        std::move(listener_remote));
+    sync_service_->AddChangeListener(kDefaultUrlWithPath, net::SiteForCookies(),
+                                     kDefaultOrigin,
+                                     std::move(listener_remote));
     TestCookieChangeListener listener(std::move(receiver));
 
     EXPECT_THAT(listener.observed_changes(), IsEmpty());
@@ -1409,26 +1333,21 @@
       listener_remote.InitWithNewPipeAndPassReceiver();
 
   // Use a cross-site site_for_cookies.
-  service_->OverrideSiteForCookiesForTesting(
-      net::SiteForCookies::FromUrl(GURL("https://not-example.com")));
-  sync_service_->AddChangeListener(
-      GURL("https://example.com/test/"),
-      GURL("https://not-example.com") /* site_for_cookies */,
-      url::Origin::Create(GURL("https://example.com")),
-      std::move(listener_remote));
+  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
+  sync_service_->AddChangeListener(kDefaultUrlWithPath, net::SiteForCookies(),
+                                   kDefaultOrigin, std::move(listener_remote));
   TestCookieChangeListener listener(std::move(receiver));
 
   ASSERT_THAT(listener.observed_changes(), IsEmpty());
 
-  GURL cookie_url("https://example.com");
   auto cookie = net::CanonicalCookie::Create(
-      cookie_url, "cookie_with_no_samesite=unspecified", base::Time::Now(),
+      kDefaultUrl, "cookie_with_no_samesite=unspecified", base::Time::Now(),
       base::nullopt);
 
   // Set cookie directly into the CookieMonster, using all-inclusive options.
   net::ResultSavingCookieCallback<net::CookieAccessResult> callback;
   cookie_monster_.SetCanonicalCookieAsync(
-      std::move(cookie), cookie_url, net::CookieOptions::MakeAllInclusive(),
+      std::move(cookie), kDefaultUrl, net::CookieOptions::MakeAllInclusive(),
       callback.MakeCallback());
   callback.WaitUntilDone();
   ASSERT_TRUE(callback.result().status.IsInclude());
@@ -1457,31 +1376,25 @@
       listener_remote.InitWithNewPipeAndPassReceiver();
 
   // Use a cross-site site_for_cookies.
-  service_->OverrideSiteForCookiesForTesting(
-      net::SiteForCookies::FromUrl(GURL("https://not-example.com")));
-  sync_service_->AddChangeListener(
-      GURL("https://example.com/test/"),
-      GURL("https://not-example.com") /* site_for_cookies */,
-      url::Origin::Create(GURL("https://example.com")),
-      std::move(listener_remote));
+  service_->OverrideIsolationInfoForTesting(kOtherIsolationInfo);
+  sync_service_->AddChangeListener(kDefaultUrlWithPath, net::SiteForCookies(),
+                                   kDefaultOrigin, std::move(listener_remote));
   TestCookieChangeListener listener(std::move(receiver));
 
   ASSERT_THAT(listener.observed_changes(), testing::SizeIs(0));
 
-  GURL cookie_url("https://example.com");
-
   auto unspecified_cookie = net::CanonicalCookie::Create(
-      cookie_url, "cookie_with_no_samesite=unspecified", base::Time::Now(),
+      kDefaultUrl, "cookie_with_no_samesite=unspecified", base::Time::Now(),
       base::nullopt);
 
   auto samesite_none_cookie = net::CanonicalCookie::Create(
-      cookie_url, "samesite_none_cookie=none; SameSite=None; Secure",
+      kDefaultUrl, "samesite_none_cookie=none; SameSite=None; Secure",
       base::Time::Now(), base::nullopt);
 
   // Set cookies directly into the CookieMonster, using all-inclusive options.
   net::ResultSavingCookieCallback<net::CookieAccessResult> callback1;
   cookie_monster_.SetCanonicalCookieAsync(
-      std::move(unspecified_cookie), cookie_url,
+      std::move(unspecified_cookie), kDefaultUrl,
       net::CookieOptions::MakeAllInclusive(), callback1.MakeCallback());
   callback1.WaitUntilDone();
   ASSERT_TRUE(callback1.result().status.IsInclude());
@@ -1493,7 +1406,7 @@
 
   net::ResultSavingCookieCallback<net::CookieAccessResult> callback2;
   cookie_monster_.SetCanonicalCookieAsync(
-      std::move(samesite_none_cookie), cookie_url,
+      std::move(samesite_none_cookie), kDefaultUrl,
       net::CookieOptions::MakeAllInclusive(), callback2.MakeCallback());
   callback2.WaitUntilDone();
   ASSERT_TRUE(callback2.result().status.IsInclude());
diff --git a/services/network/sct_auditing/DIR_METADATA b/services/network/sct_auditing/DIR_METADATA
new file mode 100644
index 0000000..ec02e3f
--- /dev/null
+++ b/services/network/sct_auditing/DIR_METADATA
@@ -0,0 +1,11 @@
+# Metadata information for this directory.
+#
+# For more information on DIR_METADATA files, see:
+#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#
+# For the schema of this file, see Metadata message:
+#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+
+monorail {
+  component: "Internals>Network>CertTrans"
+}
diff --git a/services/network/sct_auditing/OWNERS b/services/network/sct_auditing/OWNERS
new file mode 100644
index 0000000..20948b30
--- /dev/null
+++ b/services/network/sct_auditing/OWNERS
@@ -0,0 +1 @@
+cthomp@chromium.org
diff --git a/services/network/sct_auditing/README.md b/services/network/sct_auditing/README.md
new file mode 100644
index 0000000..6ae6426
--- /dev/null
+++ b/services/network/sct_auditing/README.md
@@ -0,0 +1,11 @@
+# SCT Auditing
+
+`//services/network/sct_auditing` contains the core of Chrome's implementation
+of Signed Certificate Timestamp (SCT) auditing. SCT auditing is an approach to
+verify that server certificates are being properly logged via Certificate
+Transparency (CT).
+
+The current implementation is described in the
+[Opt-in SCT Auditing design doc][1].
+
+[1]: https://docs.google.com/document/d/1G1Jy8LJgSqJ-B673GnTYIG4b7XRw2ZLtvvSlrqFcl4A/edit
diff --git a/services/network/sct_auditing_cache.cc b/services/network/sct_auditing/sct_auditing_cache.cc
similarity index 97%
rename from services/network/sct_auditing_cache.cc
rename to services/network/sct_auditing/sct_auditing_cache.cc
index 56197c5e..e8369b0 100644
--- a/services/network/sct_auditing_cache.cc
+++ b/services/network/sct_auditing/sct_auditing_cache.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "services/network/sct_auditing_cache.h"
+#include "services/network/sct_auditing/sct_auditing_cache.h"
 
 #include "base/callback.h"
 #include "base/feature_list.h"
@@ -28,8 +28,6 @@
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "net/url_request/url_request.h"
 #include "net/url_request/url_request_context.h"
-#include "services/network/network_context.h"
-#include "services/network/network_service.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/simple_url_loader.h"
 #include "services/network/public/mojom/network_context.mojom.h"
@@ -128,12 +126,11 @@
 }
 
 void SCTAuditingCache::MaybeEnqueueReport(
-    NetworkContext* context,
     const net::HostPortPair& host_port_pair,
     const net::X509Certificate* validated_certificate_chain,
     const net::SignedCertificateTimestampAndStatusList&
         signed_certificate_timestamps) {
-  if (!enabled_ || !context->is_sct_auditing_enabled())
+  if (!enabled_)
     return;
 
   auto report = std::make_unique<sct_auditing::SCTClientReport>();
diff --git a/services/network/sct_auditing_cache.h b/services/network/sct_auditing/sct_auditing_cache.h
similarity index 84%
rename from services/network/sct_auditing_cache.h
rename to services/network/sct_auditing/sct_auditing_cache.h
index f06d666..5dd65b3e 100644
--- a/services/network/sct_auditing_cache.h
+++ b/services/network/sct_auditing/sct_auditing_cache.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 SERVICES_NETWORK_SCT_AUDITING_CACHE_H_
-#define SERVICES_NETWORK_SCT_AUDITING_CACHE_H_
+#ifndef SERVICES_NETWORK_SCT_AUDITING_SCT_AUDITING_CACHE_H_
+#define SERVICES_NETWORK_SCT_AUDITING_SCT_AUDITING_CACHE_H_
 
 #include <map>
 #include <string>
@@ -17,25 +17,25 @@
 #include "net/cert/sct_auditing_delegate.h"
 #include "net/cert/signed_certificate_timestamp_and_status.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
-#include "net/url_request/url_request.h"
-#include "services/network/public/mojom/network_service.mojom.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "services/network/public/proto/sct_audit_report.pb.h"
+#include "url/gurl.h"
 
 namespace net {
 class X509Certificate;
 }
 
 namespace network {
-class NetworkContext;
 
 // SCTAuditingCache tracks SCTs seen during CT verification. The cache supports
 // a configurable sample rate to reduce load, and deduplicates SCTs seen more
 // than once. The cache evicts least-recently-used entries after it reaches its
 // capacity.
 //
-// The SCTAuditingCache also handles sending reports to a specified report URL
-// using a specific NetworkContext. These are configured by the embedder via the
-// network service's ConfigureSCTAuditing() API.
+// The SCTAuditingCache also handles sending reports to a specified report URI
+// using a specified URLLoaderFactory (for a specific NetworkContext). These
+// are configured by the embedder via the network service's
+// ConfigureSCTAuditing() API.
 //
 // A single SCTAuditingCache should be shared among all contexts that want to
 // deduplicate reports and use a single sampling mechanism. Currently, one
@@ -55,7 +55,6 @@
   // to determine whether to send a report. This means we sample a subset of
   // *certificates* rather than a subset of *connections*.
   void MaybeEnqueueReport(
-      NetworkContext* context,
       const net::HostPortPair& host_port_pair,
       const net::X509Certificate* validated_certificate_chain,
       const net::SignedCertificateTimestampAndStatusList&
@@ -64,9 +63,7 @@
   sct_auditing::SCTClientReport* GetPendingReport(
       const net::SHA256HashValue& cache_key);
 
-  // Sends the report associated with `cache_key` to `report_uri` (which is
-  // specified by the embedder). When the request completes (on success or
-  // failure), `callback` will be called with the response details.
+  // Sends the report associated with `cache_key` to the configured report URI.
   void SendReport(const net::SHA256HashValue& cache_key);
 
   void ClearCache();
@@ -111,4 +108,4 @@
 
 }  // namespace network
 
-#endif  // SERVICES_NETWORK_SCT_AUDITING_CACHE_H_
+#endif  // SERVICES_NETWORK_SCT_AUDITING_SCT_AUDITING_CACHE_H_
diff --git a/services/network/sct_auditing_cache_unittest.cc b/services/network/sct_auditing/sct_auditing_cache_unittest.cc
similarity index 78%
rename from services/network/sct_auditing_cache_unittest.cc
rename to services/network/sct_auditing/sct_auditing_cache_unittest.cc
index c3034c5..1fc84c94 100644
--- a/services/network/sct_auditing_cache_unittest.cc
+++ b/services/network/sct_auditing/sct_auditing_cache_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "services/network/sct_auditing_cache.h"
+#include "services/network/sct_auditing/sct_auditing_cache.h"
 
 #include "base/feature_list.h"
 #include "base/memory/scoped_refptr.h"
@@ -18,15 +18,11 @@
 #include "net/test/test_data_directory.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
-#include "services/network/network_context.h"
-#include "services/network/network_service.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/proto/sct_audit_report.pb.h"
-#include "services/network/test/fake_test_cert_verifier_params_factory.h"
-#include "services/network/test/test_network_context_client.h"
-
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
 
 namespace network {
 
@@ -34,43 +30,19 @@
 
 class SCTAuditingCacheTest : public testing::Test {
  public:
-  SCTAuditingCacheTest()
-      : network_service_(NetworkService::CreateForTesting()) {}
+  SCTAuditingCacheTest() {}
   ~SCTAuditingCacheTest() override = default;
 
   SCTAuditingCacheTest(const SCTAuditingCacheTest&) = delete;
   SCTAuditingCacheTest& operator=(const SCTAuditingCacheTest&) = delete;
 
   void SetUp() override {
-    InitNetworkContext();
-    network_context_->SetIsSCTAuditingEnabledForTesting(true);
     chain_ =
         net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
     ASSERT_TRUE(chain_.get());
   }
 
  protected:
-  void InitNetworkContext() {
-    mojom::NetworkContextParamsPtr params = mojom::NetworkContextParams::New();
-    // Use a dummy CertVerifier that always passes cert verification, since
-    // these unittests don't need to test CertVerifier behavior.
-    params->cert_verifier_params =
-        FakeTestCertVerifierParamsFactory::GetCertVerifierParams();
-
-    network_context_ = std::make_unique<NetworkContext>(
-        network_service_.get(),
-        network_context_remote_.BindNewPipeAndPassReceiver(),
-        std::move(params));
-
-    // A NetworkContextClient is needed for embedder notifications to work.
-    mojo::PendingRemote<network::mojom::NetworkContextClient>
-        network_context_client_remote;
-    network_context_client_ =
-        std::make_unique<network::TestNetworkContextClient>(
-            network_context_client_remote.InitWithNewPipeAndPassReceiver());
-    network_context_->SetClient(std::move(network_context_client_remote));
-  }
-
   // Initializes the configuration for the SCTAuditingCache to defaults and
   // sets up the URLLoaderFactory. Individual tests can directly call the set_*
   // methods to tweak the configuration.
@@ -94,16 +66,9 @@
 
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::MainThreadType::IO};
-  std::unique_ptr<NetworkService> network_service_;
-  std::unique_ptr<NetworkContext> network_context_;
-  std::unique_ptr<network::mojom::NetworkContextClient> network_context_client_;
   std::unique_ptr<TestURLLoaderFactory> url_loader_factory_;
 
   scoped_refptr<net::X509Certificate> chain_;
-
-  // Stores the mojo::Remote<mojom::NetworkContext> of the most recently created
-  // NetworkContext.
-  mojo::Remote<mojom::NetworkContext> network_context_remote_;
 };
 
 // Constructs a net::SignedCertificateTimestampAndStatus with the given
@@ -138,21 +103,18 @@
 
 }  // namespace
 
-// Test that if auditing is disabled on the NetworkContext, no reports are
-// cached.
+// Test that if auditing is disabled, no reports are cached.
 TEST_F(SCTAuditingCacheTest, NoReportsCachedWhenAuditingDisabled) {
   SCTAuditingCache cache(10);
   InitSCTAuditing(&cache);
-
-  network_context_->SetIsSCTAuditingEnabledForTesting(false);
+  cache.set_enabled(false);
 
   const net::HostPortPair host_port_pair("example.com", 443);
   net::SignedCertificateTimestampAndStatusList sct_list;
   MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                        "extensions1", "signature1", base::Time::Now(),
                        net::ct::SCT_STATUS_OK, &sct_list);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair, chain_.get(),
-                           sct_list);
+  cache.MaybeEnqueueReport(host_port_pair, chain_.get(), sct_list);
 
   ASSERT_EQ(0u, cache.GetCacheForTesting()->size());
 }
@@ -167,8 +129,7 @@
   MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                        "extensions1", "signature1", base::Time::Now(),
                        net::ct::SCT_STATUS_OK, &sct_list);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair, chain_.get(),
-                           sct_list);
+  cache.MaybeEnqueueReport(host_port_pair, chain_.get(), sct_list);
 
   ASSERT_EQ(1u, cache.GetCacheForTesting()->size());
 }
@@ -187,8 +148,7 @@
     MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                          "extensions1", "signature1", base::Time::Now(),
                          net::ct::SCT_STATUS_OK, &sct_list);
-    cache.MaybeEnqueueReport(network_context_.get(), host_port_pair1,
-                             chain_.get(), sct_list);
+    cache.MaybeEnqueueReport(host_port_pair1, chain_.get(), sct_list);
     ASSERT_EQ(1u, cache.GetCacheForTesting()->size());
   }
 
@@ -197,8 +157,7 @@
     MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                          "extensions1", "signature2", base::Time::Now(),
                          net::ct::SCT_STATUS_OK, &sct_list);
-    cache.MaybeEnqueueReport(network_context_.get(), host_port_pair2,
-                             chain_.get(), sct_list);
+    cache.MaybeEnqueueReport(host_port_pair2, chain_.get(), sct_list);
     ASSERT_EQ(2u, cache.GetCacheForTesting()->size());
   }
 
@@ -209,8 +168,7 @@
     MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                          "extensions1", "signature3", base::Time::Now(),
                          net::ct::SCT_STATUS_OK, &sct_list);
-    cache.MaybeEnqueueReport(network_context_.get(), host_port_pair3,
-                             chain_.get(), sct_list);
+    cache.MaybeEnqueueReport(host_port_pair3, chain_.get(), sct_list);
     ASSERT_EQ(2u, cache.GetCacheForTesting()->size());
     for (const auto& entry : *cache.GetCacheForTesting()) {
       ASSERT_NE(
@@ -233,15 +191,13 @@
   MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                        "extensions1", "signature1", base::Time::Now(),
                        net::ct::SCT_STATUS_OK, &sct_list);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair1,
-                           chain_.get(), sct_list);
+  cache.MaybeEnqueueReport(host_port_pair1, chain_.get(), sct_list);
 
   ASSERT_EQ(1u, cache.GetCacheForTesting()->size());
 
   // Enqueuing the same SCTs won't cause a new report to be added to the queue
   // (even if the connection origin is different).
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair2,
-                           chain_.get(), sct_list);
+  cache.MaybeEnqueueReport(host_port_pair2, chain_.get(), sct_list);
   ASSERT_EQ(1u, cache.GetCacheForTesting()->size());
 }
 
@@ -260,21 +216,18 @@
   MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                        "extensions1", "signature1", base::Time::Now(),
                        net::ct::SCT_STATUS_OK, &sct_list1);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair1,
-                           chain_.get(), sct_list1);
+  cache.MaybeEnqueueReport(host_port_pair1, chain_.get(), sct_list1);
 
   net::SignedCertificateTimestampAndStatusList sct_list2;
   MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                        "extensions2", "signature2", base::Time::Now(),
                        net::ct::SCT_STATUS_OK, &sct_list2);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair2,
-                           chain_.get(), sct_list2);
+  cache.MaybeEnqueueReport(host_port_pair2, chain_.get(), sct_list2);
 
   EXPECT_EQ(2u, cache.GetCacheForTesting()->size());
 
   // Try to enqueue the report for "example1.com" again. It should be deduped.
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair1,
-                           chain_.get(), sct_list1);
+  cache.MaybeEnqueueReport(host_port_pair1, chain_.get(), sct_list1);
   EXPECT_EQ(2u, cache.GetCacheForTesting()->size());
 
   // If we enqueue a new report causing the cache size limit to be exceeded,
@@ -284,8 +237,7 @@
   MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                        "extensions3", "signature3", base::Time::Now(),
                        net::ct::SCT_STATUS_OK, &sct_list3);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair3,
-                           chain_.get(), sct_list3);
+  cache.MaybeEnqueueReport(host_port_pair3, chain_.get(), sct_list3);
 
   EXPECT_EQ(2u, cache.GetCacheForTesting()->size());
   for (const auto& entry : *cache.GetCacheForTesting()) {
@@ -295,24 +247,6 @@
   }
 }
 
-TEST_F(SCTAuditingCacheTest, NoReportsCachedWhenCacheDisabled) {
-  SCTAuditingCache cache(2);
-  InitSCTAuditing(&cache);
-  cache.set_enabled(false);
-
-  // Try to enqueue a report.
-  const net::HostPortPair host_port_pair("example.com", 443);
-  net::SignedCertificateTimestampAndStatusList sct_list;
-  MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
-                       "extensions", "signature", base::Time::Now(),
-                       net::ct::SCT_STATUS_OK, &sct_list);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair, chain_.get(),
-                           sct_list);
-
-  // Check that there are no entries in the cache.
-  EXPECT_EQ(0u, cache.GetCacheForTesting()->size());
-}
-
 TEST_F(SCTAuditingCacheTest, ReportsCachedButNotSentWhenSamplingIsZero) {
   SCTAuditingCache cache(2);
   InitSCTAuditing(&cache);
@@ -324,8 +258,7 @@
   MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                        "extensions", "signature", base::Time::Now(),
                        net::ct::SCT_STATUS_OK, &sct_list);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair, chain_.get(),
-                           sct_list);
+  cache.MaybeEnqueueReport(host_port_pair, chain_.get(), sct_list);
 
   // Check that there is one entry in the cache.
   EXPECT_EQ(1u, cache.GetCacheForTesting()->size());
@@ -348,8 +281,7 @@
   MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                        "extensions", "signature", base::Time::Now(),
                        net::ct::SCT_STATUS_OK, &sct_list);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair, chain_.get(),
-                           sct_list);
+  cache.MaybeEnqueueReport(host_port_pair, chain_.get(), sct_list);
 
   // Check that there is one pending report.
   EXPECT_EQ(1, url_loader_factory()->NumPending());
@@ -381,8 +313,7 @@
   MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                        "extensions", "signature", base::Time::Now(),
                        net::ct::SCT_STATUS_OK, &sct_list);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair, chain_.get(),
-                           sct_list);
+  cache.MaybeEnqueueReport(host_port_pair, chain_.get(), sct_list);
 
   // Check that there is one pending report.
   EXPECT_EQ(1, url_loader_factory()->NumPending());
@@ -418,15 +349,13 @@
     MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                          "extensions1", "signature1", base::Time::Now(),
                          net::ct::SCT_STATUS_OK, &sct_list1);
-    cache.MaybeEnqueueReport(network_context_.get(), host_port_pair1,
-                             chain_.get(), sct_list1);
+    cache.MaybeEnqueueReport(host_port_pair1, chain_.get(), sct_list1);
 
     net::SignedCertificateTimestampAndStatusList sct_list2;
     MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                          "extensions2", "signature2", base::Time::Now(),
                          net::ct::SCT_STATUS_OK, &sct_list2);
-    cache.MaybeEnqueueReport(network_context_.get(), host_port_pair2,
-                             chain_.get(), sct_list2);
+    cache.MaybeEnqueueReport(host_port_pair2, chain_.get(), sct_list2);
 
     EXPECT_EQ(2u, cache.GetCacheForTesting()->size());
   }
@@ -450,8 +379,7 @@
   MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                        "extensions", "signature", base::Time::Now(),
                        net::ct::SCT_STATUS_OK, &sct_list);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair, chain_.get(),
-                           sct_list);
+  cache.MaybeEnqueueReport(host_port_pair, chain_.get(), sct_list);
 
   // Get the size of the enqueued report and test that it is correctly logged.
   size_t report_size =
@@ -461,8 +389,7 @@
                                 report_size, 1);
 
   // Retry enqueueing the same report which will be deduplicated.
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair, chain_.get(),
-                           sct_list);
+  cache.MaybeEnqueueReport(host_port_pair, chain_.get(), sct_list);
 
   histograms.ExpectTotalCount("Security.SCTAuditing.OptIn.ReportSampled", 1);
   histograms.ExpectTotalCount("Security.SCTAuditing.OptIn.ReportSize", 1);
@@ -484,8 +411,7 @@
   MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                        "extensions", "signature", base::Time::Now(),
                        net::ct::SCT_STATUS_OK, &sct_list);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair, chain_.get(),
-                           sct_list);
+  cache.MaybeEnqueueReport(host_port_pair, chain_.get(), sct_list);
 
   histograms.ExpectUniqueSample("Security.SCTAuditing.OptIn.ReportSampled",
                                 false, 1);
@@ -505,8 +431,7 @@
   MakeTestSCTAndStatus(net::ct::SignedCertificateTimestamp::SCT_EMBEDDED,
                        "extensions", "signature", base::Time::Now(),
                        net::ct::SCT_STATUS_INVALID_SIGNATURE, &sct_list);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair, chain_.get(),
-                           sct_list);
+  cache.MaybeEnqueueReport(host_port_pair, chain_.get(), sct_list);
 
   EXPECT_EQ(0u, cache.GetCacheForTesting()->size());
 }
@@ -530,8 +455,7 @@
       net::ct::SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION,
       "extensions3", "invalid_log", base::Time::Now(),
       net::ct::SCT_STATUS_LOG_UNKNOWN, &sct_list);
-  cache.MaybeEnqueueReport(network_context_.get(), host_port_pair, chain_.get(),
-                           sct_list);
+  cache.MaybeEnqueueReport(host_port_pair, chain_.get(), sct_list);
 
   // No invalid SCTs should be in any reports in the cache.
   for (const auto& entry : *cache.GetCacheForTesting()) {
diff --git a/services/network/test/test_network_service_client.cc b/services/network/test/test_network_service_client.cc
deleted file mode 100644
index 5765cb5..0000000
--- a/services/network/test/test_network_service_client.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2018 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 "services/network/test/test_network_service_client.h"
-
-#include <utility>
-
-#include "base/optional.h"
-#include "base/task/post_task.h"
-#include "base/unguessable_token.h"
-
-namespace network {
-
-TestNetworkServiceClient::TestNetworkServiceClient() : receiver_(nullptr) {}
-
-TestNetworkServiceClient::TestNetworkServiceClient(
-    mojo::PendingReceiver<mojom::NetworkServiceClient> receiver)
-    : receiver_(this, std::move(receiver)) {}
-
-TestNetworkServiceClient::~TestNetworkServiceClient() {}
-
-void TestNetworkServiceClient::OnDataUseUpdate(
-    int32_t network_traffic_annotation_id_hash,
-    int64_t recv_bytes,
-    int64_t sent_bytes) {}
-
-}  // namespace network
diff --git a/services/network/test/test_network_service_client.h b/services/network/test/test_network_service_client.h
deleted file mode 100644
index 5d8d9b3..0000000
--- a/services/network/test/test_network_service_client.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2018 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 SERVICES_NETWORK_TEST_TEST_NETWORK_SERVICE_CLIENT_H_
-#define SERVICES_NETWORK_TEST_TEST_NETWORK_SERVICE_CLIENT_H_
-
-#include <string>
-#include <vector>
-
-#include "build/build_config.h"
-#include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-#include "net/cookies/canonical_cookie.h"
-#include "services/network/public/mojom/network_service.mojom.h"
-
-namespace network {
-
-// A helper class with a basic NetworkServiceClient implementation for use in
-// unittests, which may need an implementation (for things like file uploads),
-// but don't have the real implementation available.
-class TestNetworkServiceClient : public network::mojom::NetworkServiceClient {
- public:
-  TestNetworkServiceClient();
-  explicit TestNetworkServiceClient(
-      mojo::PendingReceiver<mojom::NetworkServiceClient> receiver);
-  ~TestNetworkServiceClient() override;
-
-  // network::mojom::NetworkServiceClient implementation:
-  void OnDataUseUpdate(int32_t network_traffic_annotation_id_hash,
-                       int64_t recv_bytes,
-                       int64_t sent_bytes) override;
-
- private:
-  mojo::Receiver<mojom::NetworkServiceClient> receiver_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestNetworkServiceClient);
-};
-
-}  // namespace network
-
-#endif  // SERVICES_NETWORK_TEST_TEST_NETWORK_SERVICE_CLIENT_H_
diff --git a/services/network/test/test_url_loader_network_observer.cc b/services/network/test/test_url_loader_network_observer.cc
index e14055bd..8b21328 100644
--- a/services/network/test/test_url_loader_network_observer.cc
+++ b/services/network/test/test_url_loader_network_observer.cc
@@ -57,6 +57,11 @@
   std::move(callback).Run();
 }
 
+void TestURLLoaderNetworkObserver::OnDataUseUpdate(
+    int32_t network_traffic_annotation_id_hash,
+    int64_t recv_bytes,
+    int64_t sent_bytes) {}
+
 void TestURLLoaderNetworkObserver::Clone(
     mojo::PendingReceiver<URLLoaderNetworkServiceObserver> observer) {
   receivers_.Add(this, std::move(observer));
diff --git a/services/network/test/test_url_loader_network_observer.h b/services/network/test/test_url_loader_network_observer.h
index 0e11250..dc2db1cc9 100644
--- a/services/network/test/test_url_loader_network_observer.h
+++ b/services/network/test/test_url_loader_network_observer.h
@@ -52,6 +52,9 @@
                        OnClearSiteDataCallback callback) override;
   void OnLoadingStateUpdate(mojom::LoadInfoPtr info,
                             OnLoadingStateUpdateCallback callback) override;
+  void OnDataUseUpdate(int32_t network_traffic_annotation_id_hash,
+                       int64_t recv_bytes,
+                       int64_t sent_bytes) override;
   void Clone(
       mojo::PendingReceiver<URLLoaderNetworkServiceObserver> observer) override;
 
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index 1892e4d0..cdc7208 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -47,7 +47,6 @@
 #include "net/url_request/url_request_context_getter.h"
 #include "services/network/chunked_data_pipe_upload_data_stream.h"
 #include "services/network/data_pipe_element_reader.h"
-#include "services/network/network_usage_accumulator.h"
 #include "services/network/origin_policy/origin_policy_constants.h"
 #include "services/network/origin_policy/origin_policy_manager.h"
 #include "services/network/public/cpp/constants.h"
@@ -451,7 +450,6 @@
 URLLoader::URLLoader(
     net::URLRequestContext* url_request_context,
     URLLoaderFactory* url_loader_factory,
-    mojom::NetworkServiceClient* network_service_client,
     mojom::NetworkContextClient* network_context_client,
     DeleteCallback delete_callback,
     mojo::PendingReceiver<mojom::URLLoader> url_loader_receiver,
@@ -466,7 +464,6 @@
     bool require_network_isolation_key,
     scoped_refptr<ResourceSchedulerClient> resource_scheduler_client,
     base::WeakPtr<KeepaliveStatisticsRecorder> keepalive_statistics_recorder,
-    base::WeakPtr<NetworkUsageAccumulator> network_usage_accumulator,
     mojom::TrustedURLLoaderHeaderClient* url_loader_header_client,
     mojom::OriginPolicyManager* origin_policy_manager,
     std::unique_ptr<TrustTokenRequestHelperFactory> trust_token_helper_factory,
@@ -477,7 +474,6 @@
     mojo::PendingRemote<mojom::DevToolsObserver> devtools_observer)
     : url_request_context_(url_request_context),
       url_loader_factory_(url_loader_factory),
-      network_service_client_(network_service_client),
       network_context_client_(network_context_client),
       delete_callback_(std::move(delete_callback)),
       options_(options),
@@ -506,7 +502,6 @@
       request_destination_(request.destination),
       resource_scheduler_client_(std::move(resource_scheduler_client)),
       keepalive_statistics_recorder_(std::move(keepalive_statistics_recorder)),
-      network_usage_accumulator_(std::move(network_usage_accumulator)),
       first_auth_attempt_(true),
       custom_proxy_pre_cache_headers_(request.custom_proxy_pre_cache_headers),
       custom_proxy_post_cache_headers_(request.custom_proxy_post_cache_headers),
@@ -1087,7 +1082,8 @@
 }
 
 int URLLoader::OnConnected(net::URLRequest* url_request,
-                           const net::TransportInfo& info) {
+                           const net::TransportInfo& info,
+                           net::CompletionOnceCallback callback) {
   DCHECK_EQ(url_request, url_request_.get());
 
   DVLOG(1) << "Connection obtained for URL request to " << url_request->url()
@@ -1823,15 +1819,11 @@
     upload_progress_tracker_ = nullptr;
   }
 
-  if (network_usage_accumulator_) {
-    network_usage_accumulator_->OnBytesTransferred(
-        factory_params_->process_id, render_frame_id_,
-        url_request_->GetTotalReceivedBytes(),
-        url_request_->GetTotalSentBytes());
-  }
-  if (network_service_client_ && (url_request_->GetTotalReceivedBytes() > 0 ||
-                                  url_request_->GetTotalSentBytes() > 0)) {
-    network_service_client_->OnDataUseUpdate(
+  auto* url_loader_network_observer = GetURLLoaderNetworkServiceObserver();
+  if (url_loader_network_observer &&
+      (url_request_->GetTotalReceivedBytes() > 0 ||
+       url_request_->GetTotalSentBytes() > 0)) {
+    url_loader_network_observer->OnDataUseUpdate(
         url_request_->traffic_annotation().unique_id_hash_code,
         url_request_->GetTotalReceivedBytes(),
         url_request_->GetTotalSentBytes());
diff --git a/services/network/url_loader.h b/services/network/url_loader.h
index 2bd39a1..7c21a65a 100644
--- a/services/network/url_loader.h
+++ b/services/network/url_loader.h
@@ -68,7 +68,6 @@
 constexpr size_t kMaxFileUploadRequestsPerBatch = 64;
 
 class NetToMojoPendingBuffer;
-class NetworkUsageAccumulator;
 class KeepaliveStatisticsRecorder;
 class ScopedThrottlingToken;
 struct OriginPolicy;
@@ -114,7 +113,6 @@
   URLLoader(
       net::URLRequestContext* url_request_context,
       URLLoaderFactory* url_loader_factory,
-      mojom::NetworkServiceClient* network_service_client,
       mojom::NetworkContextClient* network_context_client,
       DeleteCallback delete_callback,
       mojo::PendingReceiver<mojom::URLLoader> url_loader_receiver,
@@ -129,7 +127,6 @@
       bool require_network_isolation_key,
       scoped_refptr<ResourceSchedulerClient> resource_scheduler_client,
       base::WeakPtr<KeepaliveStatisticsRecorder> keepalive_statistics_recorder,
-      base::WeakPtr<NetworkUsageAccumulator> network_usage_accumulator,
       mojom::TrustedURLLoaderHeaderClient* url_loader_header_client,
       mojom::OriginPolicyManager* origin_policy_manager,
       std::unique_ptr<TrustTokenRequestHelperFactory>
@@ -154,7 +151,8 @@
 
   // net::URLRequest::Delegate implementation:
   int OnConnected(net::URLRequest* url_request,
-                  const net::TransportInfo& info) override;
+                  const net::TransportInfo& info,
+                  net::CompletionOnceCallback callback) override;
   void OnReceivedRedirect(net::URLRequest* url_request,
                           const net::RedirectInfo& redirect_info,
                           bool* defer_redirect) override;
@@ -395,8 +393,11 @@
   mojom::CookieAccessObserver* GetCookieAccessObserver() const;
 
   net::URLRequestContext* url_request_context_;
+
+  // |url_loader_factory_| is guaranteed to outlive URLLoader, so it is safe to
+  // store a raw pointer here. It can also be null in tests.
   URLLoaderFactory* const url_loader_factory_;
-  mojom::NetworkServiceClient* network_service_client_;
+
   mojom::NetworkContextClient* network_context_client_;
   DeleteCallback delete_callback_;
 
@@ -503,8 +504,6 @@
 
   base::WeakPtr<KeepaliveStatisticsRecorder> keepalive_statistics_recorder_;
 
-  base::WeakPtr<NetworkUsageAccumulator> network_usage_accumulator_;
-
   bool first_auth_attempt_;
 
   std::unique_ptr<ScopedThrottlingToken> throttling_token_;
diff --git a/services/network/url_loader_factory.cc b/services/network/url_loader_factory.cc
index 9b4fcbd..766d3433 100644
--- a/services/network/url_loader_factory.cc
+++ b/services/network/url_loader_factory.cc
@@ -21,7 +21,6 @@
 #include "services/network/cors/cors_url_loader_factory.h"
 #include "services/network/network_context.h"
 #include "services/network/network_service.h"
-#include "services/network/network_usage_accumulator.h"
 #include "services/network/public/cpp/is_potentially_trustworthy.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/mojom/network_context.mojom.h"
@@ -79,7 +78,7 @@
       coep_reporter_(std::move(params_->coep_reporter)),
       cors_url_loader_factory_(cors_url_loader_factory),
       cookie_observer_(std::move(params_->cookie_observer)),
-      url_loader_network_observer_(
+      url_loader_network_service_observer_(
           std::move(params_->url_loader_network_observer)),
       devtools_observer_(std::move(params_->devtools_observer)) {
   DCHECK(context);
@@ -151,16 +150,11 @@
     return;
   }
 
-  mojom::NetworkServiceClient* network_service_client = nullptr;
   base::WeakPtr<KeepaliveStatisticsRecorder> keepalive_statistics_recorder;
-  base::WeakPtr<NetworkUsageAccumulator> network_usage_accumulator;
   if (context_->network_service()) {
-    network_service_client = context_->network_service()->client();
     keepalive_statistics_recorder = context_->network_service()
                                         ->keepalive_statistics_recorder()
                                         ->AsWeakPtr();
-    network_usage_accumulator =
-        context_->network_service()->network_usage_accumulator()->AsWeakPtr();
   }
 
   bool exhausted = false;
@@ -275,8 +269,7 @@
   }
 
   auto loader = std::make_unique<URLLoader>(
-      context_->url_request_context(), this, network_service_client,
-      context_->client(),
+      context_->url_request_context(), this, context_->client(),
       base::BindOnce(&cors::CorsURLLoaderFactory::DestroyURLLoader,
                      base::Unretained(cors_url_loader_factory_)),
       std::move(receiver), options, url_request, std::move(client),
@@ -285,7 +278,6 @@
       request_id, keepalive_request_size,
       context_->require_network_isolation_key(), resource_scheduler_client_,
       std::move(keepalive_statistics_recorder),
-      std::move(network_usage_accumulator),
       header_client_.is_bound() ? header_client_.get() : nullptr,
       context_->origin_policy_manager(), std::move(trust_token_factory),
       context_->cors_origin_access_list(), std::move(cookie_observer),
@@ -313,9 +305,12 @@
 
 mojom::URLLoaderNetworkServiceObserver*
 URLLoaderFactory::GetURLLoaderNetworkServiceObserver() const {
-  if (url_loader_network_observer_)
-    return url_loader_network_observer_.get();
-  return nullptr;
+  if (url_loader_network_service_observer_)
+    return url_loader_network_service_observer_.get();
+  if (!context_->network_service())
+    return nullptr;
+  return context_->network_service()
+      ->GetDefaultURLLoaderNetworkServiceObserver();
 }
 
 }  // namespace network
diff --git a/services/network/url_loader_factory.h b/services/network/url_loader_factory.h
index cf5a843..f0e423b 100644
--- a/services/network/url_loader_factory.h
+++ b/services/network/url_loader_factory.h
@@ -85,7 +85,7 @@
 
   mojo::Remote<mojom::CookieAccessObserver> cookie_observer_;
   mojo::Remote<mojom::URLLoaderNetworkServiceObserver>
-      url_loader_network_observer_;
+      url_loader_network_service_observer_;
   mojo::Remote<mojom::DevToolsObserver> devtools_observer_;
 
   DISALLOW_COPY_AND_ASSIGN(URLLoaderFactory);
diff --git a/services/network/url_loader_unittest.cc b/services/network/url_loader_unittest.cc
index 9bab1c3f..71536ec 100644
--- a/services/network/url_loader_unittest.cc
+++ b/services/network/url_loader_unittest.cc
@@ -92,7 +92,6 @@
 #include "services/network/resource_scheduler/resource_scheduler_client.h"
 #include "services/network/test/test_data_pipe_getter.h"
 #include "services/network/test/test_network_context_client.h"
-#include "services/network/test/test_network_service_client.h"
 #include "services/network/test/test_url_loader_client.h"
 #include "services/network/test/test_url_loader_network_observer.h"
 #include "services/network/test_chunked_data_pipe_getter.h"
@@ -415,7 +414,9 @@
 
  private:
   void StartAsync() {
-    const int result = NotifyConnected(transport_info_);
+    const int result =
+        NotifyConnected(transport_info_, base::DoNothing::Once<int>());
+
     if (result != net::OK) {
       NotifyStartError(result);
       return;
@@ -798,18 +799,16 @@
         net::IsolationInfo::CreateForInternalRequest(origin);
     params.is_trusted = true;
     url_loader = std::make_unique<URLLoader>(
-        context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr, network_context_client.get(),
+        context(), /*url_loader_factory=*/nullptr, network_context_client.get(),
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.BindNewPipeAndPassReceiver(), options, request,
         client_.CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
         /*coep_reporter=*/nullptr, 0 /* request_id */,
         0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        mojo::NullRemote() /* cookie_observer */,
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
         url_loader_network_observer
             ? url_loader_network_observer->Bind()
             : mojo::NullRemote() /* url_loader_network_observer */,
@@ -1844,15 +1843,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -1904,15 +1901,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -1966,15 +1961,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), 0, request, client()->CreateRemote(),
       TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
       0 /* request_id */, 0 /* keepalive_request_size */,
       false /* require_network_isolation_key */, resource_scheduler_client(),
-      nullptr, nullptr /* network_usage_accumulator */,
-      nullptr /* header_client */, nullptr /* origin_policy_manager */,
+      nullptr, nullptr /* header_client */, nullptr /* origin_policy_manager */,
       nullptr /* trust_token_helper */, kEmptyOriginAccessList,
       mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -2050,15 +2043,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), 0, request, client()->CreateRemote(),
       TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
       0 /* request_id */, 0 /* keepalive_request_size */,
       false /* require_network_isolation_key */, resource_scheduler_client(),
-      nullptr, nullptr /* network_usage_accumulator */,
-      nullptr /* header_client */, nullptr /* origin_policy_manager */,
+      nullptr, nullptr /* header_client */, nullptr /* origin_policy_manager */,
       nullptr /* trust_token_helper */, kEmptyOriginAccessList,
       mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -2123,15 +2114,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), 0, request, client()->CreateRemote(),
       TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
       0 /* request_id */, 0 /* keepalive_request_size */,
       false /* require_network_isolation_key */, resource_scheduler_client(),
-      nullptr, nullptr /* network_usage_accumulator */,
-      nullptr /* header_client */, nullptr /* origin_policy_manager */,
+      nullptr, nullptr /* header_client */, nullptr /* origin_policy_manager */,
       nullptr /* trust_token_helper */, kEmptyOriginAccessList,
       mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -2191,15 +2180,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), 0, request, client()->CreateRemote(),
       TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
       0 /* request_id */, 0 /* keepalive_request_size */,
       false /* require_network_isolation_key */, resource_scheduler_client(),
-      nullptr, nullptr /* network_usage_accumulator */,
-      nullptr /* header_client */, nullptr /* origin_policy_manager */,
+      nullptr, nullptr /* header_client */, nullptr /* origin_policy_manager */,
       nullptr /* trust_token_helper */, kEmptyOriginAccessList,
       mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -2449,15 +2436,13 @@
   auto network_context_client =
       std::make_unique<CallbackSavingNetworkContextClient>();
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
-      context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */, network_context_client.get(),
+      context(), /*url_loader_factory=*/nullptr, network_context_client.get(),
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), 0, request, client()->CreateRemote(),
       TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
       0 /* request_id */, 0 /* keepalive_request_size */,
       false /* require_network_isolation_key */, resource_scheduler_client(),
-      nullptr, nullptr /* network_usage_accumulator */,
-      nullptr /* header_client */, nullptr /* origin_policy_manager */,
+      nullptr, nullptr /* header_client */, nullptr /* origin_policy_manager */,
       nullptr /* trust_token_helper */, kEmptyOriginAccessList,
       mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -2584,7 +2569,6 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), 0, request, client()->CreateRemote(),
@@ -2592,8 +2576,7 @@
       0 /* request_id */, 0 /* keepalive_request_size */,
       false /* require_network_isolation_key */,
       nullptr /* resource_scheduler_client */,
-      nullptr /* keepalive_statistics_reporter */,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      nullptr /* keepalive_statistics_reporter */, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -2639,7 +2622,6 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), 0, request, client()->CreateRemote(),
@@ -2647,8 +2629,7 @@
       0 /* request_id */, 0 /* keepalive_request_size */,
       false /* require_network_isolation_key */,
       nullptr /* resource_scheduler_client */,
-      nullptr /* keepalive_statistics_reporter */,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      nullptr /* keepalive_statistics_reporter */, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -2751,8 +2732,7 @@
   TestURLLoaderNetworkObserver url_loader_network_observer;
   url_loader_network_observer.set_ignore_certificate_errors(true);
   url_loader = std::make_unique<URLLoader>(
-      context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */, network_context_client.get(),
+      context(), /*url_loader_factory=*/nullptr, network_context_client.get(),
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(),
       mojom::kURLLoadOptionSendSSLInfoWithResponse |
@@ -2760,8 +2740,7 @@
       request, client.CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       url_loader_network_observer.Bind() /* url_loader_network_observer */,
@@ -2789,15 +2768,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -2849,7 +2826,6 @@
     params.is_corb_enabled = false;
     url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        nullptr /* network_service_client */,
         nullptr /* network_context_client */,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
@@ -2857,10 +2833,9 @@
         /*coep_reporter=*/nullptr, 0 /* request_id */,
         0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        mojo::NullRemote() /* cookie_observer */,
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         /*devtools_observer=*/mojo::NullRemote());
 
@@ -2895,15 +2870,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
       client.CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -2954,15 +2927,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -3003,15 +2974,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -3056,15 +3025,13 @@
   params.process_id = mojom::kBrowserProcessId;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -3113,15 +3080,13 @@
   params.process_id = mojom::kBrowserProcessId;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -3179,15 +3144,13 @@
   params.process_id = mojom::kBrowserProcessId;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -3251,15 +3214,13 @@
   params.process_id = mojom::kBrowserProcessId;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
       request, client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -3289,15 +3250,13 @@
   params.process_id = mojom::kBrowserProcessId;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
       request, client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -3397,7 +3356,6 @@
 
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        nullptr /* network_service_client */,
         nullptr /* network_context_client */,
         NeverInvokedDeleteLoaderCallback(),
         loader_remote.InitWithNewPipeAndPassReceiver(), 0, request,
@@ -3405,10 +3363,9 @@
         /*coep_reporter=*/nullptr, 0 /* request_id */,
         0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        mojo::NullRemote() /* cookie_observer */,
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         /*devtools_observer=*/mojo::NullRemote());
 
@@ -3427,14 +3384,12 @@
   mojo::PendingRemote<mojom::URLLoader> loader_remote;
   std::unique_ptr<URLLoader> loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */, NeverInvokedDeleteLoaderCallback(),
       loader_remote.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -3469,15 +3424,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
       request, client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -3754,15 +3707,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_auth_observer.Bind() /* url_loader_network_observer */,
@@ -3804,15 +3755,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_auth_observer.Bind() /* url_loader_network_observer */,
@@ -3854,15 +3803,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_auth_observer.Bind() /* url_loader_network_observer */,
@@ -3905,15 +3852,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_auth_observer.Bind() /* url_loader_network_observer */,
@@ -3955,15 +3900,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_auth_observer.Bind() /* url_loader_network_observer */,
@@ -4002,15 +3945,13 @@
   mojom::URLLoaderFactoryParams params;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -4049,15 +3990,13 @@
   // what we primarily want to cover in this test.
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -4090,15 +4029,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      nullptr /* network_service_client */,
       nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
       request, client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -4197,15 +4134,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_cert_observer.Bind() /* url_loader_network_observer */,
@@ -4254,15 +4189,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), 0, request, client()->CreateRemote(),
       TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
       0 /* request_id */, 0 /* keepalive_request_size */,
       false /* require_network_isolation_key */, resource_scheduler_client(),
-      nullptr, nullptr /* network_usage_accumulator */,
-      nullptr /* header_client */, nullptr /* origin_policy_manager */,
+      nullptr, nullptr /* header_client */, nullptr /* origin_policy_manager */,
       nullptr /* trust_token_helper */, kEmptyOriginAccessList,
       mojo::NullRemote() /* cookie_observer */,
       client_cert_observer.Bind() /* url_loader_network_observer */,
@@ -4302,15 +4235,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), 0, request, client()->CreateRemote(),
       TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
       0 /* request_id */, 0 /* keepalive_request_size */,
       false /* require_network_isolation_key */, resource_scheduler_client(),
-      nullptr, nullptr /* network_usage_accumulator */,
-      nullptr /* header_client */, nullptr /* origin_policy_manager */,
+      nullptr, nullptr /* header_client */, nullptr /* origin_policy_manager */,
       nullptr /* trust_token_helper */, kEmptyOriginAccessList,
       mojo::NullRemote() /* cookie_observer */,
       client_cert_observer.Bind() /* url_loader_network_observer */,
@@ -4349,15 +4280,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_cert_observer.Bind() /* url_loader_network_observer */,
@@ -4405,15 +4334,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_cert_observer.Bind() /* url_loader_network_observer */,
@@ -4465,15 +4392,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_cert_observer.Bind() /* url_loader_network_observer */,
@@ -4527,15 +4452,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_cert_observer.Bind() /* url_loader_network_observer */,
@@ -4570,15 +4493,14 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr, nullptr /* network_context_client */,
+      nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(),
       mojom::kURLLoadOptionBlockAllCookies, request, client()->CreateRemote(),
       TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
       0 /* request_id */, 0 /* keepalive_request_size */,
       false /* require_network_isolation_key */, resource_scheduler_client(),
-      nullptr, nullptr /* network_usage_accumulator */,
-      nullptr /* header_client */, nullptr /* origin_policy_manager */,
+      nullptr, nullptr /* header_client */, nullptr /* origin_policy_manager */,
       nullptr /* trust_token_helper */, kEmptyOriginAccessList,
       mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -4602,15 +4524,14 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr, nullptr /* network_context_client */,
+      nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(),
       mojom::kURLLoadOptionBlockThirdPartyCookies, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -4634,14 +4555,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr, nullptr /* network_context_client */,
+      nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
       request, client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -4690,15 +4610,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_cert_observer.Bind() /* url_loader_network_observer */,
@@ -4749,15 +4667,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_cert_observer.Bind() /* url_loader_network_observer */,
@@ -4810,15 +4726,13 @@
   params.is_corb_enabled = false;
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       client_cert_observer.Bind() /* url_loader_network_observer */,
@@ -4846,7 +4760,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -4855,10 +4768,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        cookie_observer.GetRemote(),
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, cookie_observer.GetRemote(),
         mojo::NullRemote() /* url_loader_network_observer */,
         /*devtools_observer=*/mojo::NullRemote());
 
@@ -4887,7 +4799,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -4896,10 +4807,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        cookie_observer.GetRemote(),
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, cookie_observer.GetRemote(),
         mojo::NullRemote() /* url_loader_network_observer */,
         /*devtools_observer=*/mojo::NullRemote());
 
@@ -4928,7 +4838,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -4937,10 +4846,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        cookie_observer.GetRemote(),
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, cookie_observer.GetRemote(),
         mojo::NullRemote() /* url_loader_network_observer */,
         /*devtools_observer=*/mojo::NullRemote());
 
@@ -4980,15 +4888,13 @@
   params.is_corb_enabled = false;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /*network_context_client=*/nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
       loader_client.CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, cookie_observer.GetRemote(),
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -5031,7 +4937,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -5040,10 +4945,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        cookie_observer.GetRemote(),
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, cookie_observer.GetRemote(),
         client_auth_observer.Bind() /* url_loader_network_observer */,
         /*devtools_observer=*/mojo::NullRemote());
 
@@ -5084,7 +4988,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -5093,10 +4996,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        mojo::NullRemote() /* cookie_observer */,
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         devtools_observer.Bind());
 
@@ -5139,7 +5041,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -5148,10 +5049,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        mojo::NullRemote() /* cookie_observer */,
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         devtools_observer.Bind());
 
@@ -5186,7 +5086,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -5195,10 +5094,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        mojo::NullRemote() /* cookie_observer */,
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         devtools_observer.Bind());
 
@@ -5236,7 +5134,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -5245,10 +5142,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        mojo::NullRemote() /* cookie_observer */,
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         devtools_observer.Bind());
 
@@ -5288,7 +5184,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
@@ -5298,10 +5193,9 @@
         /*coep_reporter=*/nullptr, 0 /* request_id */,
         0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        mojo::NullRemote() /* cookie_observer */,
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         devtools_observer.Bind());
 
@@ -5347,7 +5241,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
@@ -5357,10 +5250,9 @@
         /*coep_reporter=*/nullptr, 0 /* request_id */,
         0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        mojo::NullRemote() /* cookie_observer */,
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         devtools_observer.Bind());
 
@@ -5401,7 +5293,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -5410,10 +5301,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        mojo::NullRemote() /* cookie_observer */,
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         devtools_observer.Bind());
 
@@ -5453,7 +5343,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -5462,10 +5351,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        mojo::NullRemote() /* cookie_observer */,
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         devtools_observer.Bind());
 
@@ -5500,7 +5388,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -5509,10 +5396,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        mojo::NullRemote() /* cookie_observer */,
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         devtools_observer.Bind());
 
@@ -5559,7 +5445,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -5568,10 +5453,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        cookie_observer.GetRemote(),
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, cookie_observer.GetRemote(),
         mojo::NullRemote() /* url_loader_network_observer */,
         /*devtools_observer=*/mojo::NullRemote());
 
@@ -5621,7 +5505,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -5630,10 +5513,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        cookie_observer.GetRemote(),
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, cookie_observer.GetRemote(),
         mojo::NullRemote() /* url_loader_network_observer */,
         /*devtools_observer=*/mojo::NullRemote());
 
@@ -5671,7 +5553,6 @@
     params.is_corb_enabled = false;
     std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        /*network_service_client=*/nullptr,
         /*network_context_client=*/nullptr,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone,
@@ -5680,10 +5561,9 @@
         TRAFFIC_ANNOTATION_FOR_TESTS, &params, /*coep_reporter=*/nullptr,
         0 /* request_id */, 0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, nullptr /* origin_policy_manager */,
-        nullptr /* trust_token_helper */, kEmptyOriginAccessList,
-        cookie_observer.GetRemote(),
+        nullptr, nullptr /* header_client */,
+        nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
+        kEmptyOriginAccessList, cookie_observer.GetRemote(),
         mojo::NullRemote() /* url_loader_network_observer */,
         /*devtools_observer=*/mojo::NullRemote());
 
@@ -5771,7 +5651,6 @@
 
     url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        nullptr /* network_service_client */,
         nullptr /* network_context_client */,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), 0, request,
@@ -5781,8 +5660,7 @@
         /*coep_reporter=*/nullptr, 0 /* request_id */,
         0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, &mock_origin_policy_manager,
+        nullptr, nullptr /* header_client */, &mock_origin_policy_manager,
         nullptr /* trust_token_helper */, kEmptyOriginAccessList,
         mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
@@ -5830,7 +5708,6 @@
 
     url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        nullptr /* network_service_client */,
         nullptr /* network_context_client */,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), 0, request,
@@ -5840,8 +5717,7 @@
         /*coep_reporter=*/nullptr, 0 /* request_id */,
         0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, &mock_origin_policy_manager,
+        nullptr, nullptr /* header_client */, &mock_origin_policy_manager,
         nullptr /* trust_token_helper */, kEmptyOriginAccessList,
         mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
@@ -5876,7 +5752,6 @@
 
     url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        nullptr /* network_service_client */,
         nullptr /* network_context_client */,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), 0, request,
@@ -5886,8 +5761,7 @@
         /*coep_reporter=*/nullptr, 0 /* request_id */,
         0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, &mock_origin_policy_manager,
+        nullptr, nullptr /* header_client */, &mock_origin_policy_manager,
         nullptr /* trust_token_helper */, kEmptyOriginAccessList,
         mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
@@ -5928,7 +5802,6 @@
 
     url_loader = std::make_unique<URLLoader>(
         context(), /*url_loader_factory=*/nullptr,
-        nullptr /* network_service_client */,
         nullptr /* network_context_client */,
         DeleteLoaderCallback(&delete_run_loop, &url_loader),
         loader.InitWithNewPipeAndPassReceiver(), 0, request,
@@ -5938,8 +5811,7 @@
         /*coep_reporter=*/nullptr, 0 /* request_id */,
         0 /* keepalive_request_size */,
         false /* require_network_isolation_key */, resource_scheduler_client(),
-        nullptr, nullptr /* network_usage_accumulator */,
-        nullptr /* header_client */, &mock_origin_policy_manager,
+        nullptr, nullptr /* header_client */, &mock_origin_policy_manager,
         nullptr /* trust_token_helper */, kEmptyOriginAccessList,
         mojo::NullRemote() /* cookie_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
@@ -6227,14 +6099,13 @@
 
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr, nullptr /* network_context_client */,
+      nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader_remote.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */,
       std::make_unique<MockTrustTokenRequestHelperFactory>(
           mojom::TrustTokenOperationStatus::kOk /* on_begin */,
@@ -6286,14 +6157,13 @@
 
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr, nullptr /* network_context_client */,
+      nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader_remote.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */,
       std::make_unique<MockTrustTokenRequestHelperFactory>(
           mojom::TrustTokenOperationStatus::kAlreadyExists /* on_begin */,
@@ -6333,14 +6203,13 @@
 
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr, nullptr /* network_context_client */,
+      nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader_remote.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */,
       std::make_unique<MockTrustTokenRequestHelperFactory>(
           mojom::TrustTokenOperationStatus::kFailedPrecondition /* on_begin */,
@@ -6380,14 +6249,13 @@
 
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr, nullptr /* network_context_client */,
+      nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader_remote.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */,
       std::make_unique<MockTrustTokenRequestHelperFactory>(
           mojom::TrustTokenOperationStatus::kOk /* on_begin */,
@@ -6427,14 +6295,13 @@
 
   url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr, nullptr /* network_context_client */,
+      nullptr /* network_context_client */,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader_remote.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */,
       std::make_unique<MockTrustTokenRequestHelperFactory>(
           mojom::TrustTokenOperationStatus::
@@ -6476,15 +6343,13 @@
   mojo::PendingRemote<mojom::URLLoader> loader;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /* network_context_client */ nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -6526,15 +6391,13 @@
   mojo::PendingRemote<mojom::URLLoader> loader;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /* network_context_client */ nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -6568,15 +6431,13 @@
   mojo::PendingRemote<mojom::URLLoader> loader;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /* network_context_client */ nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList, mojo::NullRemote() /* cookie_observer */,
       mojo::NullRemote() /* url_loader_network_observer */,
@@ -6603,15 +6464,13 @@
   mojo::PendingRemote<mojom::URLLoader> loader;
   std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
       context(), /*url_loader_factory=*/nullptr,
-      /*network_service_client=*/nullptr,
       /* network_context_client */ nullptr,
       DeleteLoaderCallback(&delete_run_loop, &url_loader),
       loader.InitWithNewPipeAndPassReceiver(), 0, request,
       client()->CreateRemote(), TRAFFIC_ANNOTATION_FOR_TESTS, &params,
       /*coep_reporter=*/nullptr, 0 /* request_id */,
       0 /* keepalive_request_size */, false /* require_network_isolation_key */,
-      resource_scheduler_client(), nullptr,
-      nullptr /* network_usage_accumulator */, nullptr /* header_client */,
+      resource_scheduler_client(), nullptr, nullptr /* header_client */,
       nullptr /* origin_policy_manager */, nullptr /* trust_token_helper */,
       kEmptyOriginAccessList /* origin_access_list */,
       mojo::NullRemote() /* cookie_observer */,
diff --git a/services/strings/BUILD.gn b/services/strings/BUILD.gn
index 3e5f023..73cb787 100644
--- a/services/strings/BUILD.gn
+++ b/services/strings/BUILD.gn
@@ -9,6 +9,6 @@
   source = "../services_strings.grd"
   outputs =
       [ "grit/services_strings.h" ] +
-      process_file_template(locales_with_fake_bidi,
+      process_file_template(locales_with_pseudolocales,
                             [ "services_strings_{{source_name_part}}.pak" ])
 }
diff --git a/services/tracing/public/cpp/background_tracing/background_tracing_agent_impl.cc b/services/tracing/public/cpp/background_tracing/background_tracing_agent_impl.cc
index c5612437d..7448f3f 100644
--- a/services/tracing/public/cpp/background_tracing/background_tracing_agent_impl.cc
+++ b/services/tracing/public/cpp/background_tracing/background_tracing_agent_impl.cc
@@ -20,6 +20,16 @@
 constexpr base::TimeDelta kMinTimeBetweenHistogramChanges =
     base::TimeDelta::FromSeconds(10);
 
+void RunOrPostTask(const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+                   const base::Location& from_here,
+                   base::OnceClosure task) {
+  if (task_runner->RunsTasksInCurrentSequence()) {
+    std::move(task).Run();
+    return;
+  }
+  task_runner->PostTask(from_here, std::move(task));
+}
+
 }  // namespace
 
 BackgroundTracingAgentImpl::BackgroundTracingAgentImpl(
@@ -104,8 +114,8 @@
   if (actual_value < histogram_lower_value ||
       actual_value > histogram_upper_value) {
     if (!repeat) {
-      task_runner->PostTask(
-          FROM_HERE,
+      RunOrPostTask(
+          task_runner, FROM_HERE,
           base::BindOnce(
               &BackgroundTracingAgentImpl::SendAbortBackgroundTracingMessage,
               weak_self));
@@ -120,9 +130,9 @@
                 new_sample->set_sample(actual_value);
               });
 
-  task_runner->PostTask(
-      FROM_HERE, base::BindOnce(&BackgroundTracingAgentImpl::SendTriggerMessage,
-                                weak_self, histogram_name));
+  RunOrPostTask(task_runner, FROM_HERE,
+                base::BindOnce(&BackgroundTracingAgentImpl::SendTriggerMessage,
+                               weak_self, histogram_name));
 }
 
 void BackgroundTracingAgentImpl::SendTriggerMessage(
diff --git a/services/tracing/public/cpp/background_tracing/background_tracing_agent_impl_unittest.cc b/services/tracing/public/cpp/background_tracing/background_tracing_agent_impl_unittest.cc
index 2bcaa9e5..6d05244 100644
--- a/services/tracing/public/cpp/background_tracing/background_tracing_agent_impl_unittest.cc
+++ b/services/tracing/public/cpp/background_tracing/background_tracing_agent_impl_unittest.cc
@@ -67,6 +67,8 @@
 
   BackgroundTracingAgentClientRecorder* recorder() const { return recorder_; }
 
+  void RunUntilIdle() { task_environment_.RunUntilIdle(); }
+
  private:
   base::test::TaskEnvironment task_environment_;
   mojo::Remote<tracing::mojom::BackgroundTracingAgentProvider> provider_;
@@ -79,7 +81,7 @@
 };
 
 TEST_F(BackgroundTracingAgentImplTest, TestInitialize) {
-  base::RunLoop().RunUntilIdle();
+  RunUntilIdle();
   EXPECT_EQ(1, recorder()->on_initialized_count());
 }
 
@@ -88,19 +90,56 @@
 
   agent()->SetUMACallback("foo1", 20000, 25000, true);
 
-  base::RunLoop().RunUntilIdle();
+  RunUntilIdle();
 
   EXPECT_EQ(1, recorder()->on_initialized_count());
   EXPECT_EQ(0, recorder()->on_trigger_background_trace_count());
   EXPECT_EQ(0, recorder()->on_abort_background_trace_count());
 }
 
-TEST_F(BackgroundTracingAgentImplTest, TestHistogramTriggers) {
+TEST_F(BackgroundTracingAgentImplTest, TestHistogramTriggers_ExistingSample) {
+  // Ensure that a sample exists by the time SetUMACallback is processed.
   LOCAL_HISTOGRAM_COUNTS("foo2", 2);
 
   agent()->SetUMACallback("foo2", 1, 3, true);
 
-  base::RunLoop().RunUntilIdle();
+  // RunLoop ensures that SetUMACallback and OnTriggerBackgroundTrace mojo
+  // messages are processed.
+  RunUntilIdle();
+
+  EXPECT_EQ(1, recorder()->on_initialized_count());
+  EXPECT_EQ(1, recorder()->on_trigger_background_trace_count());
+  EXPECT_EQ(0, recorder()->on_abort_background_trace_count());
+  EXPECT_EQ("foo2", recorder()->on_trigger_background_trace_histogram_name());
+}
+
+TEST_F(BackgroundTracingAgentImplTest, TestHistogramTriggers_SameThread) {
+  agent()->SetUMACallback("foo2", 1, 3, true);
+  // RunLoop ensures that SetUMACallback mojo message is processed.
+  RunUntilIdle();
+
+  LOCAL_HISTOGRAM_COUNTS("foo2", 2);
+
+  // RunLoop ensures that OnTriggerBackgroundTrace mojo message is processed.
+  RunUntilIdle();
+
+  EXPECT_EQ(1, recorder()->on_initialized_count());
+  EXPECT_EQ(1, recorder()->on_trigger_background_trace_count());
+  EXPECT_EQ(0, recorder()->on_abort_background_trace_count());
+  EXPECT_EQ("foo2", recorder()->on_trigger_background_trace_histogram_name());
+}
+
+TEST_F(BackgroundTracingAgentImplTest, TestHistogramTriggers_CrossThread) {
+  agent()->SetUMACallback("foo2", 1, 3, true);
+  // RunLoop ensures that SetUMACallback mojo message is processed.
+  RunUntilIdle();
+
+  base::ThreadPool::PostTask(
+      FROM_HERE, base::BindOnce([]() { LOCAL_HISTOGRAM_COUNTS("foo2", 2); }));
+
+  // RunLoop ensures that ThreadPool::PostTask and OnTriggerBackgroundTrace mojo
+  // message are processed.
+  RunUntilIdle();
 
   EXPECT_EQ(1, recorder()->on_initialized_count());
   EXPECT_EQ(1, recorder()->on_trigger_background_trace_count());
@@ -113,7 +152,7 @@
 
   agent()->SetUMACallback("foo3", 1, 3, false);
 
-  base::RunLoop().RunUntilIdle();
+  RunUntilIdle();
 
   EXPECT_EQ(1, recorder()->on_initialized_count());
   EXPECT_EQ(0, recorder()->on_trigger_background_trace_count());
diff --git a/skia/ext/benchmarking_canvas.cc b/skia/ext/benchmarking_canvas.cc
index cefad19c..be76da8 100644
--- a/skia/ext/benchmarking_canvas.cc
+++ b/skia/ext/benchmarking_canvas.cc
@@ -137,10 +137,9 @@
 std::unique_ptr<base::Value> AsValue(const SkColorFilter& filter) {
   std::unique_ptr<base::DictionaryValue> val(new base::DictionaryValue());
 
-  if (unsigned flags = filter.getFlags()) {
+  if (filter.isAlphaUnchanged()) {
     FlagsBuilder builder('|');
-    builder.addFlag(flags & SkColorFilter::kAlphaUnchanged_Flag,
-                    "kAlphaUnchanged_Flag");
+    builder.addFlag(true, "kAlphaUnchanged_Flag");
 
     val->SetString("flags", builder.str());
   }
diff --git a/testing/android/reporter/BUILD.gn b/testing/android/reporter/BUILD.gn
index 5828637..804f805 100644
--- a/testing/android/reporter/BUILD.gn
+++ b/testing/android/reporter/BUILD.gn
@@ -14,7 +14,6 @@
   ]
 
   sources = [
-    "java/src/org/chromium/test/reporter/TestStatusListener.java",
     "java/src/org/chromium/test/reporter/TestStatusReceiver.java",
     "java/src/org/chromium/test/reporter/TestStatusReporter.java",
   ]
diff --git a/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusListener.java b/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusListener.java
deleted file mode 100644
index a807af3..0000000
--- a/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusListener.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2015 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.
-
-package org.chromium.test.reporter;
-
-import android.content.Context;
-import android.util.Log;
-
-import junit.framework.AssertionFailedError;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestListener;
-
-/**
- * A TestListener that reports when tests start, pass, or fail.
- */
-public class TestStatusListener implements TestListener {
-
-    private static final String TAG = "TestStatusListener";
-
-    private boolean mFailed;
-    private final TestStatusReporter mReporter;
-    private Throwable mThrowable;
-
-    public TestStatusListener(Context context) {
-        mReporter = new TestStatusReporter(context);
-    }
-
-    /** Called when an error has occurred while running a test.
-
-        Note that an error usually means a problem with the test or test harness, not with
-        the code under test.
-
-        @param test The test in which the error occurred.
-        @param t The exception that was raised.
-     */
-    @Override
-    public void addError(Test test, Throwable t) {
-        Log.e(TAG, "Error while running " + test.toString(), t);
-        mFailed = true;
-        mThrowable = t;
-    }
-
-    /** Called when a test has failed.
-
-        @param test The test in which the failure occurred.
-        @param t The exception that was raised.
-     */
-    @Override
-    public void addFailure(Test test, AssertionFailedError e) {
-        Log.e(TAG, "Failure while running " + test.toString(), e);
-        mFailed = true;
-        mThrowable = e;
-    }
-
-    /** Called when a test has started.
-        @param test The test that started.
-     */
-    @Override
-    public void startTest(Test test) {
-        mFailed = false;
-        TestCase testCase = (TestCase) test;
-        mReporter.startHeartbeat();
-        mReporter.testStarted(testCase.getClass().getName(), testCase.getName());
-    }
-
-    /** Called when a test has ended.
-        @param test The test that ended.
-     */
-    @Override
-    public void endTest(Test test) {
-        TestCase testCase = (TestCase) test;
-        if (mFailed) {
-            String stackTrace = null;
-            if (mThrowable != null) {
-                stackTrace = Log.getStackTraceString(mThrowable);
-            }
-            mReporter.testFailed(testCase.getClass().getName(), testCase.getName(), stackTrace);
-        } else {
-            mReporter.testPassed(testCase.getClass().getName(), testCase.getName());
-        }
-        mReporter.stopHeartbeat();
-    }
-}
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 45e9825..291b8fbe 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -68,6 +68,4209 @@
       "all"
     ]
   },
+  "android-11-x86-fyi-rel": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "absl_hardening_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "absl_hardening_tests",
+        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "android_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "android_browsertests",
+        "test_id_prefix": "ninja://chrome/test:android_browsertests/"
+      },
+      {
+        "args": [
+          "--test-launcher-batch-limit=1",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "android_sync_integration_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "android_sync_integration_tests",
+        "test_id_prefix": "ninja://chrome/test:android_sync_integration_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "android_webview_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "android_webview_unittests",
+        "test_id_prefix": "ninja://android_webview/test:android_webview_unittests/"
+      },
+      {
+        "args": [
+          "angle_unittests",
+          "-v",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "angle_unittests",
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/",
+        "use_isolated_scripts_api": true
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "base_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "base_util_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_util_unittests",
+        "test_id_prefix": "ninja://base/util:base_util_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_common_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_heap_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_heap_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_platform_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webkit_unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "webkit_unit_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "boringssl_crypto_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_crypto_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "boringssl_ssl_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_ssl_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "breakpad_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "breakpad_unittests",
+        "test_id_prefix": "ninja://third_party/breakpad:breakpad_unittests/"
+      },
+      {
+        "args": [
+          "--gtest_filter=-*UsingRealWebcam*",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "capture_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "capture_unittests",
+        "test_id_prefix": "ninja://media/capture:capture_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "cast_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "cc_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "chrome_java_test_pagecontroller_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "chrome_java_test_pagecontroller_tests",
+        "test_id_prefix": "ninja://chrome/test/android:chrome_java_test_pagecontroller_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "chrome_java_test_webapk_launch_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "chrome_java_test_webapk_launch_tests",
+        "test_id_prefix": "ninja://chrome/test/android:chrome_java_test_webapk_launch_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "chrome_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "chrome_public_smoke_test",
+        "test_id_prefix": "ninja://chrome/android:chrome_public_smoke_test/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--git-revision=${got_revision}"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "chrome_public_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chrome-gold@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 20
+        },
+        "test": "chrome_public_test_apk",
+        "test_id_prefix": "ninja://chrome/android:chrome_public_test_apk/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "components_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "components_browsertests",
+        "test_id_prefix": "ninja://components:components_browsertests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "components_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 20
+        },
+        "test": "content_browsertests",
+        "test_id_prefix": "ninja://content/test:content_browsertests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_shell_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test": "content_shell_test_apk",
+        "test_id_prefix": "ninja://content/shell/android:content_shell_test_apk/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "crashpad_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crashpad_tests",
+        "test_id_prefix": "ninja://third_party/crashpad/crashpad:crashpad_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "crypto_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crypto_unittests",
+        "test_id_prefix": "ninja://crypto:crypto_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "device_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "device_unittests",
+        "test_id_prefix": "ninja://device:device_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "display_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "display_unittests",
+        "test_id_prefix": "ninja://ui/display:display_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "events_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "events_unittests",
+        "test_id_prefix": "ninja://ui/events:events_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gcm_unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gcm_unit_tests",
+        "test_id_prefix": "ninja://google_apis/gcm:gcm_unit_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gfx_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gfx_unittests",
+        "test_id_prefix": "ninja://ui/gfx:gfx_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gin_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gin_unittests",
+        "test_id_prefix": "ninja://gin:gin_unittests/"
+      },
+      {
+        "args": [
+          "--use-cmd-decoder=validating",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gl_tests_validating"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "gl_tests_validating",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gl_tests",
+        "test_id_prefix": "ninja://gpu:gl_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gl_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gl_unittests",
+        "test_id_prefix": "ninja://ui/gl:gl_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "google_apis_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "google_apis_unittests",
+        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gpu_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gwp_asan_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gwp_asan_unittests",
+        "test_id_prefix": "ninja://components/gwp_asan:gwp_asan_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "ipc_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ipc_tests",
+        "test_id_prefix": "ninja://ipc:ipc_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "jingle_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "jingle_unittests",
+        "test_id_prefix": "ninja://jingle:jingle_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "latency_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "latency_unittests",
+        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "libjingle_xmpp_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "libjingle_xmpp_unittests",
+        "test_id_prefix": "ninja://third_party/libjingle_xmpp:libjingle_xmpp_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "liburlpattern_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "liburlpattern_unittests",
+        "test_id_prefix": "ninja://third_party/liburlpattern:liburlpattern_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "media_blink_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_blink_unittests",
+        "test_id_prefix": "ninja://media/blink:media_blink_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "media_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_unittests",
+        "test_id_prefix": "ninja://media:media_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "midi_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "midi_unittests",
+        "test_id_prefix": "ninja://media/midi:midi_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "mojo_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_test_apk",
+        "test_id_prefix": "ninja://mojo/public/java/system:mojo_test_apk/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "mojo_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_unittests",
+        "test_id_prefix": "ninja://mojo:mojo_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "monochrome_public_bundle_fake_modules_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "monochrome_public_bundle_fake_modules_smoke_test",
+        "test_id_prefix": "ninja://chrome/android:monochrome_public_bundle_fake_modules_smoke_test/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "monochrome_public_bundle_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "monochrome_public_bundle_smoke_test",
+        "test_id_prefix": "ninja://chrome/android:monochrome_public_bundle_smoke_test/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "monochrome_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "monochrome_public_smoke_test",
+        "test_id_prefix": "ninja://chrome/android:monochrome_public_smoke_test/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "net_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test": "net_unittests",
+        "test_id_prefix": "ninja://net:net_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "sandbox_linux_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sandbox_linux_unittests",
+        "test_id_prefix": "ninja://sandbox/linux:sandbox_linux_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "services_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "shell_dialogs_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "shell_dialogs_unittests",
+        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "skia_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "skia_unittests",
+        "test_id_prefix": "ninja://skia:skia_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "sql_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sql_unittests",
+        "test_id_prefix": "ninja://sql:sql_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "storage_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "storage_unittests",
+        "test_id_prefix": "ninja://storage:storage_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "ui_android_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_android_unittests",
+        "test_id_prefix": "ninja://ui/android:ui_android_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "ui_base_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_base_unittests",
+        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "ui_touch_selection_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_touch_selection_unittests",
+        "test_id_prefix": "ninja://ui/touch_selection:ui_touch_selection_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "unit_tests",
+        "test_id_prefix": "ninja://chrome/test:unit_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "url_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "url_unittests",
+        "test_id_prefix": "ninja://url:url_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "viz_unittests",
+        "test_id_prefix": "ninja://components/viz:viz_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "weblayer_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "weblayer_browsertests",
+        "test_id_prefix": "ninja://weblayer/test:weblayer_browsertests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "weblayer_private_instrumentation_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "weblayer_private_instrumentation_test_apk",
+        "test_id_prefix": "ninja://weblayer/browser/android/javatests:weblayer_private_instrumentation_test_apk/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "weblayer_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "weblayer_unittests",
+        "test_id_prefix": "ninja://weblayer/test:weblayer_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webview_instrumentation_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 7
+        },
+        "test": "webview_instrumentation_test_apk",
+        "test_id_prefix": "ninja://android_webview/test:webview_instrumentation_test_apk/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "wtf_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "zlib_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "zlib_unittests",
+        "test_id_prefix": "ninja://third_party/zlib:zlib_unittests/"
+      }
+    ]
+  },
   "android-pie-arm64-wpt-rel-non-cq": {
     "isolated_scripts": [
       {
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 5f7dbad..ec633923 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -30122,7 +30122,7 @@
       {
         "args": [
           "angle_gles1_conformance_tests",
-          "--test-timeout=40",
+          "--test-timeout=60",
           "--bot-mode"
         ],
         "merge": {
@@ -38162,7 +38162,7 @@
       {
         "args": [
           "angle_gles1_conformance_tests",
-          "--test-timeout=40",
+          "--test-timeout=60",
           "--bot-mode"
         ],
         "merge": {
diff --git a/testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter b/testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter
index 7603816..0e57593d 100644
--- a/testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter
+++ b/testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter
@@ -93,6 +93,3 @@
 # crbug.com/1169250
 -org.chromium.chrome.browser.omnibox.LocationBarTest.testFocusLogic_keyboardVisibility
 
-# crbug.com/1183540: Re-enable once the test sharding is optimized for batched tests,
-# and does not cause timeout.
--org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.*
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index cee5145..5928d436 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -145,6 +145,25 @@
       ]
     },
   },
+  '11-x86-emulator': {
+    '$mixin_append': {
+      'args': [
+        '--avd-config=../../tools/android/avd/proto/generic_android30.textpb',
+      ],
+    },
+    'swarming': {
+      'named_caches': [
+        {
+          'name': 'avd_generic_android30',
+          'path': '.android',
+        },
+        {
+          'name': 'system_images_android_30_google_apis_x86',
+          'path': '.emulator_sdk',
+        },
+      ]
+    },
+  },
   'android_q': {
     'swarming': {
       'dimensions': {
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index cca6db3..b80b522e 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -100,6 +100,20 @@
       },
     },
   },
+  'angle_gles1_conformance_tests': {
+    'replacements': {
+      'Win10 FYI x64 Debug (NVIDIA)': {
+        'args': {
+          '--test-timeout': '60',
+        },
+      },
+      'Win7 FYI Debug (AMD)': {
+        'args': {
+          '--test-timeout': '60',
+        },
+      },
+    },
+  },
   'angle_perftests': {
     'remove_from': [
       'Android FYI Release (Nexus 5)',  # crbug.com/915429
@@ -867,6 +881,7 @@
       'Lollipop Phone Tester',
       'Lollipop Tablet Tester',
       'Marshmallow Tablet Tester',
+      'android-11-x86-fyi-rel',
       'android-lollipop-arm-rel',
       'android-marshmallow-x86-rel-non-cq',
       'android-pie-x86-rel',
@@ -944,6 +959,15 @@
       'Win10 Tests x64 (dbg)',
     ],
   },
+  'components_browsertests': {
+    'modifications': {
+      'android-11-x86-fyi-rel': {
+        'swarming': {
+          'shards': 2,
+        },
+      },
+    },
+  },
   'components_unittests': {
     'remove_from': [
       # https://crbug.com/1147531 - covered on lollipop
@@ -1077,6 +1101,11 @@
           '--disable-features=WebRTC-H264WithOpenH264FFmpeg',
         ],
       },
+      'android-11-x86-fyi-rel': {
+        'swarming': {
+          'shards': 20,
+        },
+      },
       'android-arm64-proguard-rel': {
         'swarming': {
           'shards': 16,
@@ -2192,6 +2221,7 @@
       'android-nougat-arm64-rel',
       'android-pie-arm64-rel',
       'android-pie-x86-rel',
+      'android-11-x86-fyi-rel',
       'Android CFI',
       'Lollipop Phone Tester',
       'Lollipop Tablet Tester',
@@ -2485,6 +2515,11 @@
       },
     },
   },
+  'system_webview_shell_layout_test_apk': {
+    'remove_from': [
+      'android-11-x86-fyi-rel', # crbug.com/1165280
+    ],
+  },
   'tab_capture_end2end_tests': {
     # Run these only on Release bots.
     'remove_from': [
@@ -2787,6 +2822,7 @@
       'android-lollipop-arm-rel',
       'android-marshmallow-x86-rel-non-cq',
       'android-pie-x86-rel',
+      'android-11-x86-fyi-rel',
     ],
   },
   'vr_common_unittests': {
@@ -2936,11 +2972,13 @@
   'weblayer_bundle_test': {
     'remove_from': [
       'android-marshmallow-x86-rel-non-cq', # crbug.com/1088013
+      'android-11-x86-fyi-rel', # crbug.com/1165280
     ],
   },
   'weblayer_instrumentation_test_apk': {
     'remove_from': [
       'android-marshmallow-x86-rel-non-cq', # crbug.com/1088013
+      'android-11-x86-fyi-rel', # crbug.com/1165280
     ],
     'modifications': {
       'android-pie-arm64-rel': {
@@ -2986,6 +3024,9 @@
     },
   },
   'webview_cts_tests': {
+    'remove_from': [
+      'android-11-x86-fyi-rel', # crbug.com/1165280
+    ],
     'modifications': {
       'android-pie-arm64-rel': {
         # TODO(crbug.com/1111436): Move this back to walleye if/when additional
@@ -3036,6 +3077,11 @@
       },
     },
   },
+  'webview_ui_test_app_test_apk': {
+    'remove_from': [
+      'android-11-x86-fyi-rel', # crbug.com/1165280
+    ],
+  },
   'xr_browser_tests': {
     'remove_from': [
       # This exception probably needs to stay due to lack of capacity
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 5d100fbd..dec89330 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -5532,6 +5532,22 @@
       'android_ddready_vr_gtests',
     ],
 
+    'android_11_emulator_gtests': [
+      'android_monochrome_smoke_tests',
+      'android_pagecontroller_self_tests',
+      'android_smoke_tests',
+      'android_webapk_launch_tests',
+      'android_specific_chromium_gtests',  # Already includes gl_gtests.
+      'chromium_gtests',
+      'chromium_gtests_for_devices_with_graphical_output',
+      'linux_flavor_specific_chromium_gtests',
+      'system_webview_shell_instrumentation_tests', # Not an experimental test
+      'weblayer_android_gtests',
+      'weblayer_gtests',
+      'webview_cts_tests_gtest',
+      'webview_ui_instrumentation_tests',
+    ],
+
     # This is the same as 'android_lollipop_marshmallow_gtests'
     # with the addition of 'webview_cts_tests_gtest' and
     # 'webview_ui_instrumentation_tests'
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 3b85556..fc687614 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -1204,6 +1204,18 @@
           'all',
         ],
       },
+      'android-11-x86-fyi-rel': {
+        'mixins': [
+          '11-x86-emulator',
+          'emulator-4-cores',
+          'linux-xenial',
+          'x86-64',
+        ],
+        'os_type': 'android',
+        'test_suites': {
+          'gtest_tests': 'android_11_emulator_gtests',
+        }
+      },
       'android-pie-arm64-wpt-rel-non-cq': {
         'mixins': [
           'enable_resultdb',
diff --git a/testing/test.gni b/testing/test.gni
index 2708c5f83..22c5513 100644
--- a/testing/test.gni
+++ b/testing/test.gni
@@ -237,35 +237,28 @@
     # everything added via additional_manifests
     if (!defined(invoker.manifest)) {
       if (!defined(invoker.additional_manifests)) {
-        manifest = "//build/config/fuchsia/tests-with-exec.cmx"
+        manifest_fragments = [ "//build/config/fuchsia/tests-with-exec.cmx" ]
       } else {
-        manifest = "//build/config/fuchsia/test/minimum_capabilities.test-cmx"
+        manifest_fragments =
+            [ "//build/config/fuchsia/test/minimum_capabilities.test-cmx" ]
       }
     } else {
-      manifest = invoker.manifest
+      manifest_fragments = [ invoker.manifest ]
     }
 
     if (defined(invoker.additional_manifests)) {
-      combined_manifest = "${target_name}.test-cmx"
-      cmc_merge(combined_manifest) {
-        sources = [ manifest ]
-        sources += invoker.additional_manifests
-        output_name = target_name
-      }
-      manifest = "${target_out_dir}/${combined_manifest}"
+      manifest_fragments += invoker.additional_manifests
     }
-
     if (use_clang_coverage) {
-      component_with_coverage_manifest = "${target_name}-coverage.test-cmx"
-      cmc_merge(component_with_coverage_manifest) {
-        sources = [
-          "//build/config/fuchsia/add_DebugData_service.test-cmx",
-          manifest,
-        ]
-        output_name = target_name
-      }
-      manifest = "${target_out_dir}/${component_with_coverage_manifest}"
+      manifest_fragments +=
+          [ "//build/config/fuchsia/add_DebugData_service.test-cmx" ]
     }
+    combined_manifest = "${target_name}.test-cmx"
+    cmc_merge(combined_manifest) {
+      sources = manifest_fragments
+      output_name = target_name
+    }
+    manifest = "${target_out_dir}/${combined_manifest}"
 
     fuchsia_package_runner(target_name) {
       is_test_exe = true
@@ -285,13 +278,7 @@
     cr_fuchsia_package(_pkg_target) {
       binary = ":$_exec_target"
       package_name_override = _output_name
-      deps = []
-      if (use_clang_coverage) {
-        deps += [ ":$component_with_coverage_manifest" ]
-      }
-      if (defined(invoker.additional_manifests)) {
-        deps += [ ":$combined_manifest" ]
-      }
+      deps = [ ":$combined_manifest" ]
     }
 
     executable(_exec_target) {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 6df42e2..85b64379 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -920,28 +920,6 @@
             ]
         }
     ],
-    "AutofillEnableCardLinkedOffer": [
-        {
-            "platforms": [
-                "android",
-                "android_webview",
-                "chromeos",
-                "chromeos_lacros",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "SyncAutofillWalletOfferData",
-                        "kAutofillEnableOffersInDownstream"
-                    ]
-                }
-            ]
-        }
-    ],
     "AutofillEnableSupportForMoreStructureInAddresses": [
         {
             "platforms": [
@@ -1883,6 +1861,21 @@
             ]
         }
     ],
+    "ContentCapture": [
+        {
+            "platforms": [
+                "android_weblayer"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "ContentCapture"
+                    ]
+                }
+            ]
+        }
+    ],
     "ContentCaptureConstantStreaming": [
         {
             "platforms": [
@@ -5746,6 +5739,26 @@
             ]
         }
     ],
+    "PrivacySandboxSettings": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "PrivacySandboxSettings"
+                    ]
+                }
+            ]
+        }
+    ],
     "ProbabilisticCryptidRenderer": [
         {
             "platforms": [
diff --git a/third_party/abseil-cpp/CMake/AbseilDll.cmake b/third_party/abseil-cpp/CMake/AbseilDll.cmake
index 47707df..39f85f2 100644
--- a/third_party/abseil-cpp/CMake/AbseilDll.cmake
+++ b/third_party/abseil-cpp/CMake/AbseilDll.cmake
@@ -131,6 +131,7 @@
   "numeric/int128.cc"
   "numeric/int128.h"
   "numeric/internal/bits.h"
+  "numeric/internal/representation.h"
   "random/bernoulli_distribution.h"
   "random/beta_distribution.h"
   "random/bit_gen_ref.h"
diff --git a/third_party/abseil-cpp/README.chromium b/third_party/abseil-cpp/README.chromium
index 17bcd563..492340d 100644
--- a/third_party/abseil-cpp/README.chromium
+++ b/third_party/abseil-cpp/README.chromium
@@ -4,7 +4,7 @@
 License: Apache 2.0
 License File: LICENSE
 Version: 0
-Revision: b0735979d778a768caee207f01f327535cbd2140
+Revision: ab21820d47e4f83875dda008b600514d3520fd35
 Security Critical: yes
 
 Description:
diff --git a/third_party/abseil-cpp/absl/container/btree_benchmark.cc b/third_party/abseil-cpp/absl/container/btree_benchmark.cc
index 46798676..41f13f5 100644
--- a/third_party/abseil-cpp/absl/container/btree_benchmark.cc
+++ b/third_party/abseil-cpp/absl/container/btree_benchmark.cc
@@ -101,39 +101,6 @@
   BM_InsertImpl<T>(state, true);
 }
 
-// container::insert sometimes returns a pair<iterator, bool> and sometimes
-// returns an iterator (for multi- containers).
-template <typename Iter>
-Iter GetIterFromInsert(const std::pair<Iter, bool>& pair) {
-  return pair.first;
-}
-template <typename Iter>
-Iter GetIterFromInsert(const Iter iter) {
-  return iter;
-}
-
-// Benchmark insertion of values into a container at the end.
-template <typename T>
-void BM_InsertEnd(benchmark::State& state) {
-  using V = typename remove_pair_const<typename T::value_type>::type;
-  typename KeyOfValue<typename T::key_type, V>::type key_of_value;
-
-  T container;
-  const int kSize = 10000;
-  for (int i = 0; i < kSize; ++i) {
-    container.insert(Generator<V>(kSize)(i));
-  }
-  V v = Generator<V>(kSize)(kSize - 1);
-  typename T::key_type k = key_of_value(v);
-
-  auto it = container.find(k);
-  while (state.KeepRunning()) {
-    // Repeatedly removing then adding v.
-    container.erase(it);
-    it = GetIterFromInsert(container.insert(v));
-  }
-}
-
 // Benchmark inserting the first few elements in a container. In b-tree, this is
 // when the root node grows.
 template <typename T>
@@ -513,7 +480,6 @@
 #define MY_BENCHMARK3(type)               \
   MY_BENCHMARK4(type, Insert);            \
   MY_BENCHMARK4(type, InsertSorted);      \
-  MY_BENCHMARK4(type, InsertEnd);         \
   MY_BENCHMARK4(type, InsertSmall);       \
   MY_BENCHMARK4(type, Lookup);            \
   MY_BENCHMARK4(type, FullLookup);        \
diff --git a/third_party/abseil-cpp/absl/numeric/BUILD.bazel b/third_party/abseil-cpp/absl/numeric/BUILD.bazel
index 5d7b1857..ea587bfa 100644
--- a/third_party/abseil-cpp/absl/numeric/BUILD.bazel
+++ b/third_party/abseil-cpp/absl/numeric/BUILD.bazel
@@ -101,3 +101,15 @@
         "@com_github_google_benchmark//:benchmark_main",
     ],
 )
+
+cc_library(
+    name = "representation",
+    hdrs = [
+        "internal/representation.h",
+    ],
+    copts = ABSL_DEFAULT_COPTS,
+    linkopts = ABSL_DEFAULT_LINKOPTS,
+    deps = [
+        "//absl/base:config",
+    ],
+)
diff --git a/third_party/abseil-cpp/absl/numeric/BUILD.gn b/third_party/abseil-cpp/absl/numeric/BUILD.gn
index 5e2558a..6368869 100644
--- a/third_party/abseil-cpp/absl/numeric/BUILD.gn
+++ b/third_party/abseil-cpp/absl/numeric/BUILD.gn
@@ -28,3 +28,8 @@
     "//third_party/abseil-cpp/absl/base:core_headers",
   ]
 }
+
+absl_source_set("representation") {
+  public = [ "internal/representation.h" ]
+  deps = [ "//third_party/abseil-cpp/absl/base:config" ]
+}
diff --git a/third_party/abseil-cpp/absl/numeric/CMakeLists.txt b/third_party/abseil-cpp/absl/numeric/CMakeLists.txt
index be94352a..781987dc8 100644
--- a/third_party/abseil-cpp/absl/numeric/CMakeLists.txt
+++ b/third_party/abseil-cpp/absl/numeric/CMakeLists.txt
@@ -86,3 +86,15 @@
     absl::int128
   PUBLIC
 )
+
+absl_cc_library(
+  NAME
+    numeric_representation
+  HDRS
+    "internal/representation.h"
+  COPTS
+    ${ABSL_DEFAULT_COPTS}
+  DEPS
+    absl::config
+  PUBLIC
+)
diff --git a/third_party/abseil-cpp/absl/numeric/internal/representation.h b/third_party/abseil-cpp/absl/numeric/internal/representation.h
new file mode 100644
index 0000000..82d332f
--- /dev/null
+++ b/third_party/abseil-cpp/absl/numeric/internal/representation.h
@@ -0,0 +1,55 @@
+// Copyright 2021 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_NUMERIC_INTERNAL_REPRESENTATION_H_
+#define ABSL_NUMERIC_INTERNAL_REPRESENTATION_H_
+
+#include <limits>
+
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace numeric_internal {
+
+// Returns true iff long double is represented as a pair of doubles added
+// together.
+inline constexpr bool IsDoubleDouble() {
+  // A double-double value always has exactly twice the precision of a double
+  // value--one double carries the high digits and one double carries the low
+  // digits. This property is not shared with any other common floating-point
+  // representation, so this test won't trigger false positives. For reference,
+  // this table gives the number of bits of precision of each common
+  // floating-point representation:
+  //
+  //                type     precision
+  //         IEEE single          24 b
+  //         IEEE double          53
+  //     x86 long double          64
+  //       double-double         106
+  //      IEEE quadruple         113
+  //
+  // Note in particular that a quadruple-precision float has greater precision
+  // than a double-double float despite taking up the same amount of memory; the
+  // quad has more of its bits allocated to the mantissa than the double-double
+  // has.
+  return std::numeric_limits<long double>::digits ==
+         2 * std::numeric_limits<double>::digits;
+}
+
+}  // namespace numeric_internal
+ABSL_NAMESPACE_END
+}  // namespace absl
+
+#endif  // ABSL_NUMERIC_INTERNAL_REPRESENTATION_H_
diff --git a/third_party/abseil-cpp/absl/random/BUILD.bazel b/third_party/abseil-cpp/absl/random/BUILD.bazel
index d97b2c4e..66ffcbc 100644
--- a/third_party/abseil-cpp/absl/random/BUILD.bazel
+++ b/third_party/abseil-cpp/absl/random/BUILD.bazel
@@ -188,6 +188,7 @@
         ":distributions",
         ":random",
         "//absl/base:raw_logging_internal",
+        "//absl/numeric:representation",
         "//absl/random/internal:distribution_test_util",
         "//absl/random/internal:pcg_engine",
         "//absl/random/internal:sequence_urbg",
@@ -308,6 +309,7 @@
         ":random",
         "//absl/base:core_headers",
         "//absl/base:raw_logging_internal",
+        "//absl/numeric:representation",
         "//absl/random/internal:distribution_test_util",
         "//absl/random/internal:pcg_engine",
         "//absl/random/internal:sequence_urbg",
@@ -331,6 +333,7 @@
         ":random",
         "//absl/base:core_headers",
         "//absl/base:raw_logging_internal",
+        "//absl/numeric:representation",
         "//absl/random/internal:distribution_test_util",
         "//absl/random/internal:sequence_urbg",
         "//absl/strings",
@@ -377,6 +380,7 @@
         ":distributions",
         ":random",
         "//absl/base:raw_logging_internal",
+        "//absl/numeric:representation",
         "//absl/random/internal:distribution_test_util",
         "//absl/random/internal:pcg_engine",
         "//absl/random/internal:sequence_urbg",
diff --git a/third_party/abseil-cpp/absl/random/CMakeLists.txt b/third_party/abseil-cpp/absl/random/CMakeLists.txt
index 13093d6..3009a034 100644
--- a/third_party/abseil-cpp/absl/random/CMakeLists.txt
+++ b/third_party/abseil-cpp/absl/random/CMakeLists.txt
@@ -259,6 +259,7 @@
   LINKOPTS
     ${ABSL_DEFAULT_LINKOPTS}
   DEPS
+    absl::numeric_representation
     absl::random_distributions
     absl::random_random
     absl::random_internal_distribution_test_util
@@ -381,6 +382,7 @@
     ${ABSL_DEFAULT_LINKOPTS}
   DEPS
     absl::core_headers
+    absl::numeric_representation
     absl::random_distributions
     absl::random_internal_distribution_test_util
     absl::random_internal_pcg_engine
@@ -404,6 +406,7 @@
     ${ABSL_DEFAULT_LINKOPTS}
   DEPS
     absl::core_headers
+    absl::numeric_representation
     absl::random_distributions
     absl::random_internal_distribution_test_util
     absl::random_internal_sequence_urbg
@@ -446,6 +449,7 @@
   LINKOPTS
     ${ABSL_DEFAULT_LINKOPTS}
   DEPS
+    absl::numeric_representation
     absl::random_distributions
     absl::random_internal_distribution_test_util
     absl::random_internal_pcg_engine
diff --git a/third_party/abseil-cpp/absl/random/beta_distribution_test.cc b/third_party/abseil-cpp/absl/random/beta_distribution_test.cc
index 277e4dc..44cdfdd0 100644
--- a/third_party/abseil-cpp/absl/random/beta_distribution_test.cc
+++ b/third_party/abseil-cpp/absl/random/beta_distribution_test.cc
@@ -21,12 +21,14 @@
 #include <random>
 #include <sstream>
 #include <string>
+#include <type_traits>
 #include <unordered_map>
 #include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/base/internal/raw_logging.h"
+#include "absl/numeric/internal/representation.h"
 #include "absl/random/internal/chi_square.h"
 #include "absl/random/internal/distribution_test_util.h"
 #include "absl/random/internal/pcg_engine.h"
@@ -42,7 +44,15 @@
 template <typename IntType>
 class BetaDistributionInterfaceTest : public ::testing::Test {};
 
-using RealTypes = ::testing::Types<float, double, long double>;
+// double-double arithmetic is not supported well by either GCC or Clang; see
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99048,
+// https://bugs.llvm.org/show_bug.cgi?id=49131, and
+// https://bugs.llvm.org/show_bug.cgi?id=49132. Don't bother running these tests
+// with double doubles until compiler support is better.
+using RealTypes =
+    std::conditional<absl::numeric_internal::IsDoubleDouble(),
+                     ::testing::Types<float, double>,
+                     ::testing::Types<float, double, long double>>::type;
 TYPED_TEST_CASE(BetaDistributionInterfaceTest, RealTypes);
 
 TYPED_TEST(BetaDistributionInterfaceTest, SerializeTest) {
@@ -53,9 +63,6 @@
   const TypeParam kLargeA =
       std::exp(std::log((std::numeric_limits<TypeParam>::max)()) -
                std::log(std::log((std::numeric_limits<TypeParam>::max)())));
-  const TypeParam kLargeAPPC = std::exp(
-      std::log((std::numeric_limits<TypeParam>::max)()) -
-      std::log(std::log((std::numeric_limits<TypeParam>::max)())) - 10.0f);
   using param_type = typename absl::beta_distribution<TypeParam>::param_type;
 
   constexpr int kCount = 1000;
@@ -76,9 +83,6 @@
       kLargeA,                                //
       std::nextafter(kLargeA, TypeParam(0)),  //
       std::nextafter(kLargeA, std::numeric_limits<TypeParam>::max()),
-      kLargeAPPC,  //
-      std::nextafter(kLargeAPPC, TypeParam(0)),
-      std::nextafter(kLargeAPPC, std::numeric_limits<TypeParam>::max()),
       // Boundary cases.
       std::numeric_limits<TypeParam>::max(),
       std::numeric_limits<TypeParam>::epsilon(),
@@ -125,28 +129,6 @@
 
       ss >> after;
 
-#if defined(__powerpc64__) || defined(__PPC64__) || defined(__powerpc__) || \
-    defined(__ppc__) || defined(__PPC__)
-      if (std::is_same<TypeParam, long double>::value) {
-        // Roundtripping floating point values requires sufficient precision
-        // to reconstruct the exact value. It turns out that long double
-        // has some errors doing this on ppc.
-        if (alpha <= std::numeric_limits<double>::max() &&
-            alpha >= std::numeric_limits<double>::lowest()) {
-          EXPECT_EQ(static_cast<double>(before.alpha()),
-                    static_cast<double>(after.alpha()))
-              << ss.str();
-        }
-        if (beta <= std::numeric_limits<double>::max() &&
-            beta >= std::numeric_limits<double>::lowest()) {
-          EXPECT_EQ(static_cast<double>(before.beta()),
-                    static_cast<double>(after.beta()))
-              << ss.str();
-        }
-        continue;
-      }
-#endif
-
       EXPECT_EQ(before.alpha(), after.alpha());
       EXPECT_EQ(before.beta(), after.beta());
       EXPECT_EQ(before, after)           //
diff --git a/third_party/abseil-cpp/absl/random/exponential_distribution_test.cc b/third_party/abseil-cpp/absl/random/exponential_distribution_test.cc
index 5a8afde5..af11d61c1 100644
--- a/third_party/abseil-cpp/absl/random/exponential_distribution_test.cc
+++ b/third_party/abseil-cpp/absl/random/exponential_distribution_test.cc
@@ -30,6 +30,7 @@
 #include "gtest/gtest.h"
 #include "absl/base/internal/raw_logging.h"
 #include "absl/base/macros.h"
+#include "absl/numeric/internal/representation.h"
 #include "absl/random/internal/chi_square.h"
 #include "absl/random/internal/distribution_test_util.h"
 #include "absl/random/internal/pcg_engine.h"
@@ -47,7 +48,15 @@
 template <typename RealType>
 class ExponentialDistributionTypedTest : public ::testing::Test {};
 
-using RealTypes = ::testing::Types<float, double, long double>;
+// double-double arithmetic is not supported well by either GCC or Clang; see
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99048,
+// https://bugs.llvm.org/show_bug.cgi?id=49131, and
+// https://bugs.llvm.org/show_bug.cgi?id=49132. Don't bother running these tests
+// with double doubles until compiler support is better.
+using RealTypes =
+    std::conditional<absl::numeric_internal::IsDoubleDouble(),
+                     ::testing::Types<float, double>,
+                     ::testing::Types<float, double, long double>>::type;
 TYPED_TEST_CASE(ExponentialDistributionTypedTest, RealTypes);
 
 TYPED_TEST(ExponentialDistributionTypedTest, SerializeTest) {
@@ -126,23 +135,6 @@
 
     ss >> after;
 
-#if defined(__powerpc64__) || defined(__PPC64__) || defined(__powerpc__) || \
-    defined(__ppc__) || defined(__PPC__)
-    if (std::is_same<TypeParam, long double>::value) {
-      // Roundtripping floating point values requires sufficient precision to
-      // reconstruct the exact value. It turns out that long double has some
-      // errors doing this on ppc, particularly for values
-      // near {1.0 +/- epsilon}.
-      if (lambda <= std::numeric_limits<double>::max() &&
-          lambda >= std::numeric_limits<double>::lowest()) {
-        EXPECT_EQ(static_cast<double>(before.lambda()),
-                  static_cast<double>(after.lambda()))
-            << ss.str();
-      }
-      continue;
-    }
-#endif
-
     EXPECT_EQ(before.lambda(), after.lambda())  //
         << ss.str() << " "                      //
         << (ss.good() ? "good " : "")           //
diff --git a/third_party/abseil-cpp/absl/random/gaussian_distribution_test.cc b/third_party/abseil-cpp/absl/random/gaussian_distribution_test.cc
index 2aa7caf..c0bac2b 100644
--- a/third_party/abseil-cpp/absl/random/gaussian_distribution_test.cc
+++ b/third_party/abseil-cpp/absl/random/gaussian_distribution_test.cc
@@ -21,12 +21,14 @@
 #include <iterator>
 #include <random>
 #include <string>
+#include <type_traits>
 #include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/base/internal/raw_logging.h"
 #include "absl/base/macros.h"
+#include "absl/numeric/internal/representation.h"
 #include "absl/random/internal/chi_square.h"
 #include "absl/random/internal/distribution_test_util.h"
 #include "absl/random/internal/sequence_urbg.h"
@@ -43,7 +45,15 @@
 template <typename RealType>
 class GaussianDistributionInterfaceTest : public ::testing::Test {};
 
-using RealTypes = ::testing::Types<float, double, long double>;
+// double-double arithmetic is not supported well by either GCC or Clang; see
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99048,
+// https://bugs.llvm.org/show_bug.cgi?id=49131, and
+// https://bugs.llvm.org/show_bug.cgi?id=49132. Don't bother running these tests
+// with double doubles until compiler support is better.
+using RealTypes =
+    std::conditional<absl::numeric_internal::IsDoubleDouble(),
+                     ::testing::Types<float, double>,
+                     ::testing::Types<float, double, long double>>::type;
 TYPED_TEST_CASE(GaussianDistributionInterfaceTest, RealTypes);
 
 TYPED_TEST(GaussianDistributionInterfaceTest, SerializeTest) {
@@ -129,29 +139,6 @@
 
         ss >> after;
 
-#if defined(__powerpc64__) || defined(__PPC64__) || defined(__powerpc__) || \
-    defined(__ppc__) || defined(__PPC__)
-        if (std::is_same<TypeParam, long double>::value) {
-          // Roundtripping floating point values requires sufficient precision
-          // to reconstruct the exact value.  It turns out that long double
-          // has some errors doing this on ppc, particularly for values
-          // near {1.0 +/- epsilon}.
-          if (mean <= std::numeric_limits<double>::max() &&
-              mean >= std::numeric_limits<double>::lowest()) {
-            EXPECT_EQ(static_cast<double>(before.mean()),
-                      static_cast<double>(after.mean()))
-                << ss.str();
-          }
-          if (stddev <= std::numeric_limits<double>::max() &&
-              stddev >= std::numeric_limits<double>::lowest()) {
-            EXPECT_EQ(static_cast<double>(before.stddev()),
-                      static_cast<double>(after.stddev()))
-                << ss.str();
-          }
-          continue;
-        }
-#endif
-
         EXPECT_EQ(before.mean(), after.mean());
         EXPECT_EQ(before.stddev(), after.stddev())  //
             << ss.str() << " "                      //
diff --git a/third_party/abseil-cpp/absl/random/uniform_real_distribution_test.cc b/third_party/abseil-cpp/absl/random/uniform_real_distribution_test.cc
index 8cf874d..18bcd3b 100644
--- a/third_party/abseil-cpp/absl/random/uniform_real_distribution_test.cc
+++ b/third_party/abseil-cpp/absl/random/uniform_real_distribution_test.cc
@@ -20,11 +20,13 @@
 #include <random>
 #include <sstream>
 #include <string>
+#include <type_traits>
 #include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/base/internal/raw_logging.h"
+#include "absl/numeric/internal/representation.h"
 #include "absl/random/internal/chi_square.h"
 #include "absl/random/internal/distribution_test_util.h"
 #include "absl/random/internal/pcg_engine.h"
@@ -55,7 +57,15 @@
 template <typename RealType>
 class UniformRealDistributionTest : public ::testing::Test {};
 
-using RealTypes = ::testing::Types<float, double, long double>;
+// double-double arithmetic is not supported well by either GCC or Clang; see
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99048,
+// https://bugs.llvm.org/show_bug.cgi?id=49131, and
+// https://bugs.llvm.org/show_bug.cgi?id=49132. Don't bother running these tests
+// with double doubles until compiler support is better.
+using RealTypes =
+    std::conditional<absl::numeric_internal::IsDoubleDouble(),
+                     ::testing::Types<float, double>,
+                     ::testing::Types<float, double, long double>>::type;
 
 TYPED_TEST_SUITE(UniformRealDistributionTest, RealTypes);
 
diff --git a/third_party/abseil-cpp/absl/strings/BUILD.bazel b/third_party/abseil-cpp/absl/strings/BUILD.bazel
index 5efaf89..123b5ef 100644
--- a/third_party/abseil-cpp/absl/strings/BUILD.bazel
+++ b/third_party/abseil-cpp/absl/strings/BUILD.bazel
@@ -709,6 +709,7 @@
         "//absl/meta:type_traits",
         "//absl/numeric:bits",
         "//absl/numeric:int128",
+        "//absl/numeric:representation",
         "//absl/types:optional",
         "//absl/types:span",
     ],
diff --git a/third_party/abseil-cpp/absl/strings/BUILD.gn b/third_party/abseil-cpp/absl/strings/BUILD.gn
index d9d2769b..404cc03 100644
--- a/third_party/abseil-cpp/absl/strings/BUILD.gn
+++ b/third_party/abseil-cpp/absl/strings/BUILD.gn
@@ -111,6 +111,7 @@
     "//third_party/abseil-cpp/absl/meta:type_traits",
     "//third_party/abseil-cpp/absl/numeric:bits",
     "//third_party/abseil-cpp/absl/numeric:int128",
+    "//third_party/abseil-cpp/absl/numeric:representation",
     "//third_party/abseil-cpp/absl/types:optional",
     "//third_party/abseil-cpp/absl/types:span",
   ]
diff --git a/third_party/abseil-cpp/absl/strings/CMakeLists.txt b/third_party/abseil-cpp/absl/strings/CMakeLists.txt
index 12f322a94..3b7ae63 100644
--- a/third_party/abseil-cpp/absl/strings/CMakeLists.txt
+++ b/third_party/abseil-cpp/absl/strings/CMakeLists.txt
@@ -410,6 +410,7 @@
     absl::strings
     absl::config
     absl::core_headers
+    absl::numeric_representation
     absl::type_traits
     absl::int128
     absl::span
diff --git a/third_party/abseil-cpp/absl/strings/internal/str_format/float_conversion.cc b/third_party/abseil-cpp/absl/strings/internal/str_format/float_conversion.cc
index 2b1fd8c..b1c406847 100644
--- a/third_party/abseil-cpp/absl/strings/internal/str_format/float_conversion.cc
+++ b/third_party/abseil-cpp/absl/strings/internal/str_format/float_conversion.cc
@@ -29,6 +29,7 @@
 #include "absl/meta/type_traits.h"
 #include "absl/numeric/bits.h"
 #include "absl/numeric/int128.h"
+#include "absl/numeric/internal/representation.h"
 #include "absl/strings/numbers.h"
 #include "absl/types/optional.h"
 #include "absl/types/span.h"
@@ -39,6 +40,8 @@
 
 namespace {
 
+using ::absl::numeric_internal::IsDoubleDouble;
+
 // The code below wants to avoid heap allocations.
 // To do so it needs to allocate memory on the stack.
 // `StackArray` will allocate memory on the stack in the form of a uint32_t
@@ -112,13 +115,6 @@
   return next_carry % divisor;
 }
 
-constexpr bool IsDoubleDouble() {
-  // This is the `double-double` representation of `long double`.
-  // We do not handle it natively. Fallback to snprintf.
-  return std::numeric_limits<long double>::digits ==
-         2 * std::numeric_limits<double>::digits;
-}
-
 using MaxFloatType =
     typename std::conditional<IsDoubleDouble(), double, long double>::type;
 
@@ -1404,6 +1400,8 @@
 bool ConvertFloatImpl(long double v, const FormatConversionSpecImpl &conv,
                       FormatSinkImpl *sink) {
   if (IsDoubleDouble()) {
+    // This is the `double-double` representation of `long double`. We do not
+    // handle it natively. Fallback to snprintf.
     return FallbackToSnprintf(v, conv, sink);
   }
 
diff --git a/third_party/blink/common/mobile_metrics/mobile_friendliness.cc b/third_party/blink/common/mobile_metrics/mobile_friendliness.cc
index de817f31..863fcef 100644
--- a/third_party/blink/common/mobile_metrics/mobile_friendliness.cc
+++ b/third_party/blink/common/mobile_metrics/mobile_friendliness.cc
@@ -13,7 +13,8 @@
          allow_user_zoom == other.allow_user_zoom &&
          small_text_ratio == other.small_text_ratio &&
          text_content_outside_viewport_percentage ==
-             other.text_content_outside_viewport_percentage;
+             other.text_content_outside_viewport_percentage &&
+         bad_tap_targets_ratio == other.bad_tap_targets_ratio;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/common/mobile_metrics/mobile_friendliness_mojom_traits.cc b/third_party/blink/common/mobile_metrics/mobile_friendliness_mojom_traits.cc
index b2ea4ca..aa1bf19 100644
--- a/third_party/blink/common/mobile_metrics/mobile_friendliness_mojom_traits.cc
+++ b/third_party/blink/common/mobile_metrics/mobile_friendliness_mojom_traits.cc
@@ -18,6 +18,7 @@
   mf->small_text_ratio = data.small_text_ratio();
   mf->text_content_outside_viewport_percentage =
       data.text_content_outside_viewport_percentage();
+  mf->bad_tap_targets_ratio = data.bad_tap_targets_ratio();
   return true;
 }
 
diff --git a/third_party/blink/common/mobile_metrics/mobile_friendliness_mojom_traits.h b/third_party/blink/common/mobile_metrics/mobile_friendliness_mojom_traits.h
index bc6d553..67f6633 100644
--- a/third_party/blink/common/mobile_metrics/mobile_friendliness_mojom_traits.h
+++ b/third_party/blink/common/mobile_metrics/mobile_friendliness_mojom_traits.h
@@ -36,6 +36,9 @@
       const blink::MobileFriendliness& mf) {
     return mf.text_content_outside_viewport_percentage;
   }
+  static int bad_tap_targets_ratio(const blink::MobileFriendliness& mf) {
+    return mf.bad_tap_targets_ratio;
+  }
   static bool Read(blink::mojom::MobileFriendlinessDataView data,
                    blink::MobileFriendliness* mf);
 };
diff --git a/third_party/blink/public/common/mobile_metrics/mobile_friendliness.h b/third_party/blink/public/common/mobile_metrics/mobile_friendliness.h
index d1b65dc..9b83e20 100644
--- a/third_party/blink/public/common/mobile_metrics/mobile_friendliness.h
+++ b/third_party/blink/public/common/mobile_metrics/mobile_friendliness.h
@@ -47,6 +47,13 @@
   // relative to the frame width.
   // Default -1 means "Unknown" and should not be sent as UKM.
   int text_content_outside_viewport_percentage = -1;
+
+  // Percentage of tap targets whose center position is within another tap
+  // target (expanded by a margin).  The detail of the algorithm is
+  // go/bad-tap-target-ukm
+  // Default -1 means "Unknown" and should not be sent as UKM.
+  // If evaluation time budget exceeded, this will be -2.
+  int bad_tap_targets_ratio = -1;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/public/mojom/link_to_text/link_to_text.mojom b/third_party/blink/public/mojom/link_to_text/link_to_text.mojom
index 1581bf7..342f4a1 100644
--- a/third_party/blink/public/mojom/link_to_text/link_to_text.mojom
+++ b/third_party/blink/public/mojom/link_to_text/link_to_text.mojom
@@ -8,10 +8,7 @@
 // TextFragmentSelectorProducer is used for requesting renderer to generate
 // text fragment selector for the latest text selection. Implemented in renderer.
 interface TextFragmentSelectorProducer {
-  // Cancel text fragment generation if any active.
-  Cancel();
-
-  // Request text fragment selector according to
+  // Generates text fragment selector according to
   // https://github.com/WICG/scroll-to-text-fragment#proposed-solution.
-  RequestSelector() => (string selector);
+  GenerateSelector() => (string selector);
 };
diff --git a/third_party/blink/public/mojom/mobile_metrics/mobile_friendliness.mojom b/third_party/blink/public/mojom/mobile_metrics/mobile_friendliness.mojom
index 545964c6..b808d176 100644
--- a/third_party/blink/public/mojom/mobile_metrics/mobile_friendliness.mojom
+++ b/third_party/blink/public/mojom/mobile_metrics/mobile_friendliness.mojom
@@ -38,4 +38,9 @@
   // Percentage of pixels of text and images horizontally outside the viewport,
   // relative to the frame width.
   int32 text_content_outside_viewport_percentage = 0;
+
+  // Percentage of bad tap targets of all tap targets.
+  // Default value is -1 as an invalid data.
+  // If evaluation time budget exceeded, this will be -2.
+  int32 bad_tap_targets_ratio;
 };
diff --git a/third_party/blink/public/mojom/page/page.mojom b/third_party/blink/public/mojom/page/page.mojom
index 464faabf..e122d4f 100644
--- a/third_party/blink/public/mojom/page/page.mojom
+++ b/third_party/blink/public/mojom/page/page.mojom
@@ -65,9 +65,6 @@
   // web contents is adopted as a portal.
   SetInsidePortal(bool is_inside_portal);
 
-  // Notifies the renderer when the prerendering page is activated.
-  ActivatePrerender();
-
   // Notifies the renderer when updating a set of blink preferences.
   UpdateWebPreferences(blink.mojom.WebPreferences preferences);
 
diff --git a/third_party/blink/public/strings/BUILD.gn b/third_party/blink/public/strings/BUILD.gn
index 16cefa57..8b5a92f 100644
--- a/third_party/blink/public/strings/BUILD.gn
+++ b/third_party/blink/public/strings/BUILD.gn
@@ -8,6 +8,6 @@
 grit("strings") {
   source = "blink_strings.grd"
   outputs = [ "grit/blink_strings.h" ] +
-            process_file_template(locales_with_fake_bidi,
+            process_file_template(locales_with_pseudolocales,
                                   [ "blink_strings_{{source_name_part}}.pak" ])
 }
diff --git a/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h b/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h
index 8cfac1d..6c79433 100644
--- a/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h
+++ b/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h
@@ -428,6 +428,32 @@
   return array;
 }
 
+// Helper function for IDLSequence in the case using reinterpret_cast
+// In order to reduce code size, avoids template instantiation of
+// ToV8HelperSequence<T> where T is a subclass of bindings::DictionaryBase or
+// ScriptWrappable. Since these base classes are the leftmost base class,
+// HeapVector<Member<TheBase>> has the same binary representation with
+// HeapVector<Member<T>>. We leverage this fact to reduce the APK size.
+//
+// This hack reduces the APK size by 4 Kbytes as of 2021 March.
+template <typename BaseClassOfT, typename T>
+inline v8::MaybeLocal<v8::Value> ToV8HelperSequenceWithMemberUpcast(
+    ScriptState* script_state,
+    const HeapVector<Member<T>>* vector) {
+  return ToV8HelperSequence<BaseClassOfT>(
+      script_state,
+      *reinterpret_cast<const HeapVector<Member<BaseClassOfT>>*>(vector));
+}
+
+template <typename BaseClassOfT, typename T>
+inline v8::MaybeLocal<v8::Value> ToV8HelperSequenceWithMemberUpcast(
+    ScriptState* script_state,
+    const HeapVector<Member<const T>>* vector) {
+  return ToV8HelperSequence<BaseClassOfT>(
+      script_state,
+      *reinterpret_cast<const HeapVector<Member<BaseClassOfT>>*>(vector));
+}
+
 // Helper function for IDLRecord
 template <typename ValueIDLType, typename VectorType>
 inline v8::MaybeLocal<v8::Value> ToV8HelperRecord(ScriptState* script_state,
@@ -463,7 +489,130 @@
 
 // IDLSequence
 template <typename T>
-struct ToV8Traits<IDLSequence<T>> {
+struct ToV8Traits<
+    IDLSequence<T>,
+    std::enable_if_t<std::is_base_of<bindings::DictionaryBase, T>::value>> {
+  static v8::MaybeLocal<v8::Value> ToV8(ScriptState* script_state,
+                                        const HeapVector<Member<T>>& value)
+      WARN_UNUSED_RESULT {
+    return bindings::ToV8HelperSequenceWithMemberUpcast<
+        bindings::DictionaryBase>(script_state, &value);
+  }
+
+  static v8::MaybeLocal<v8::Value> ToV8(
+      ScriptState* script_state,
+      const HeapVector<Member<const T>>& value) WARN_UNUSED_RESULT {
+    return bindings::ToV8HelperSequenceWithMemberUpcast<
+        bindings::DictionaryBase>(script_state, &value);
+  }
+
+  static v8::MaybeLocal<v8::Value> ToV8(ScriptState* script_state,
+                                        const HeapVector<Member<T>>* value)
+      WARN_UNUSED_RESULT {
+    DCHECK(value);
+    return bindings::ToV8HelperSequenceWithMemberUpcast<
+        bindings::DictionaryBase>(script_state, value);
+  }
+
+  static v8::MaybeLocal<v8::Value> ToV8(
+      ScriptState* script_state,
+      const HeapVector<Member<const T>>* value) WARN_UNUSED_RESULT {
+    DCHECK(value);
+    return bindings::ToV8HelperSequenceWithMemberUpcast<
+        bindings::DictionaryBase>(script_state, value);
+  }
+
+  // TODO(crbug.com/1185046): Remove this overload.
+  static v8::MaybeLocal<v8::Value> ToV8(
+      ScriptState* script_state,
+      const Vector<v8::Local<v8::Value>>& value) WARN_UNUSED_RESULT {
+    return bindings::ToV8HelperSequence<IDLAny>(script_state, value);
+  }
+};
+
+template <typename T>
+struct ToV8Traits<
+    IDLSequence<T>,
+    std::enable_if_t<std::is_base_of<IDLDictionaryBase, T>::value>> {
+  static v8::MaybeLocal<v8::Value> ToV8(ScriptState* script_state,
+                                        const HeapVector<Member<T>>& value)
+      WARN_UNUSED_RESULT {
+    return bindings::ToV8HelperSequenceWithMemberUpcast<IDLDictionaryBase>(
+        script_state, &value);
+  }
+
+  static v8::MaybeLocal<v8::Value> ToV8(
+      ScriptState* script_state,
+      const HeapVector<Member<const T>>& value) WARN_UNUSED_RESULT {
+    return bindings::ToV8HelperSequenceWithMemberUpcast<IDLDictionaryBase>(
+        script_state, &value);
+  }
+
+  static v8::MaybeLocal<v8::Value> ToV8(ScriptState* script_state,
+                                        const HeapVector<Member<T>>* value)
+      WARN_UNUSED_RESULT {
+    DCHECK(value);
+    return bindings::ToV8HelperSequenceWithMemberUpcast<IDLDictionaryBase>(
+        script_state, value);
+  }
+
+  static v8::MaybeLocal<v8::Value> ToV8(
+      ScriptState* script_state,
+      const HeapVector<Member<const T>>* value) WARN_UNUSED_RESULT {
+    DCHECK(value);
+    return bindings::ToV8HelperSequenceWithMemberUpcast<IDLDictionaryBase>(
+        script_state, value);
+  }
+
+  // TODO(crbug.com/1185046): Remove this overload.
+  static v8::MaybeLocal<v8::Value> ToV8(
+      ScriptState* script_state,
+      const Vector<v8::Local<v8::Value>>& value) WARN_UNUSED_RESULT {
+    return bindings::ToV8HelperSequence<IDLAny>(script_state, value);
+  }
+};
+
+template <typename T>
+struct ToV8Traits<
+    IDLSequence<T>,
+    std::enable_if_t<std::is_base_of<ScriptWrappable, T>::value>> {
+  static v8::MaybeLocal<v8::Value> ToV8(ScriptState* script_state,
+                                        const HeapVector<Member<T>>& value)
+      WARN_UNUSED_RESULT {
+    return bindings::ToV8HelperSequenceWithMemberUpcast<ScriptWrappable>(
+        script_state, &value);
+  }
+
+  static v8::MaybeLocal<v8::Value> ToV8(
+      ScriptState* script_state,
+      const HeapVector<Member<const T>>& value) WARN_UNUSED_RESULT {
+    return bindings::ToV8HelperSequenceWithMemberUpcast<ScriptWrappable>(
+        script_state, &value);
+  }
+
+  static v8::MaybeLocal<v8::Value> ToV8(ScriptState* script_state,
+                                        const HeapVector<Member<T>>* value)
+      WARN_UNUSED_RESULT {
+    DCHECK(value);
+    return bindings::ToV8HelperSequenceWithMemberUpcast<ScriptWrappable>(
+        script_state, value);
+  }
+
+  static v8::MaybeLocal<v8::Value> ToV8(
+      ScriptState* script_state,
+      const HeapVector<Member<const T>>* value) WARN_UNUSED_RESULT {
+    DCHECK(value);
+    return bindings::ToV8HelperSequenceWithMemberUpcast<ScriptWrappable>(
+        script_state, value);
+  }
+};
+
+template <typename T>
+struct ToV8Traits<
+    IDLSequence<T>,
+    std::enable_if_t<!std::is_base_of<bindings::DictionaryBase, T>::value &&
+                     !std::is_base_of<IDLDictionaryBase, T>::value &&
+                     !std::is_base_of<ScriptWrappable, T>::value>> {
   template <typename VectorType>
   static v8::MaybeLocal<v8::Value> ToV8(ScriptState* script_state,
                                         const VectorType& value)
diff --git a/third_party/blink/renderer/build/scripts/scripts.gni b/third_party/blink/renderer/build/scripts/scripts.gni
index be6bab77..bfd65de2 100644
--- a/third_party/blink/renderer/build/scripts/scripts.gni
+++ b/third_party/blink/renderer/build/scripts/scripts.gni
@@ -6,7 +6,7 @@
 import("//build/toolchain/toolchain.gni")
 import("//third_party/blink/renderer/config.gni")
 
-if (is_mac) {
+if (host_os == "mac") {
   import("//build/config/mac/mac_sdk.gni")
 }
 
diff --git a/third_party/blink/renderer/controller/highest_pmf_reporter.cc b/third_party/blink/renderer/controller/highest_pmf_reporter.cc
index 10c5cd5f..c89e4519 100644
--- a/third_party/blink/renderer/controller/highest_pmf_reporter.cc
+++ b/third_party/blink/renderer/controller/highest_pmf_reporter.cc
@@ -121,8 +121,12 @@
   peak_resident_bytes_at_current_highest_pmf_ = 0.0;
   webpage_counts_at_current_highest_pmf_ = 0;
   report_count_++;
-  if (report_count_ >= base::size(time_to_report))
+  if (report_count_ >= base::size(time_to_report)) {
+    // Stop observing the MemoryUsageMonitor once there's no more histogram to
+    // report.
+    MemoryUsageMonitor::Instance().RemoveObserver(this);
     return;
+  }
 
   base::TimeDelta delay =
       time_to_report[report_count_] - time_to_report[report_count_ - 1];
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index e23940b0..dfb30a8e 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1212,6 +1212,7 @@
 source_set("unit_tests") {
   testonly = true
   sources = [
+    "app_history/app_history_test.cc",
     "clipboard/clipboard_utilities_test.cc",
     "content_capture/content_capture_test.cc",
     "css/css_revert_value_test.cc",
@@ -1564,6 +1565,8 @@
     "script/module_record_resolver_impl_test.cc",
     "script/module_script_test.cc",
     "script/script_runner_test.cc",
+    "scroll/scroll_animator_test.cc",
+    "scroll/scroll_test.cc",
     "scroll/scrollable_area_test.cc",
     "scroll/scrollbar_test_suite.h",
     "scroll/scrollbar_theme_overlay_test.cc",
@@ -1666,13 +1669,6 @@
 
   data_deps = [ ":unit_tests_data" ]
 
-  if (!is_mac) {
-    sources += [
-      "scroll/scroll_animator_test.cc",
-      "scroll/scroll_test.cc",
-    ]
-  }
-
   if (use_aura) {
     sources += [ "scroll/scrollbar_theme_aura_test.cc" ]
   }
diff --git a/third_party/blink/renderer/core/animation/compositor_animations.cc b/third_party/blink/renderer/core/animation/compositor_animations.cc
index 395e378..d106a2c4 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations.cc
@@ -868,8 +868,9 @@
     if (layout_object->IsSVGViewportContainer()) {
       // Nested SVG doesn't support transforms for now.
       reasons |= kTransformRelatedPropertyCannotBeAcceleratedOnTarget;
-    } else if (layout_object->IsSVGForeignObject() &&
-               layout_object->StyleRef().EffectiveZoom() != 1) {
+    } else if (layout_object->StyleRef().EffectiveZoom() != 1) {
+      // TODO(crbug.com/1186312): Composited transform animation with non-1
+      // effective zoom is incorrectly scaled for now.
       // TODO(crbug.com/1134775): If a foreignObject's effect zoom is not 1,
       // its transform node contains an additional scale which would be removed
       // by composited animation.
diff --git a/third_party/blink/renderer/core/animation/compositor_animations_test.cc b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
index 7fa19303..5d3ab47 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations_test.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
@@ -2242,6 +2242,9 @@
       <use id="use" href="#rect-useref" class="animate"/>
       <use id="use-offset" href="#rect-useref" x="10" class="animate"/>
     </svg>
+    <svg id="svg-zoomed" class="animate" style="zoom: 1.5">
+      <rect id="rect-zoomed" class="animate"/>
+    </svg>
   )HTML");
 
   auto CanStartAnimation = [&](const char* id) -> bool {
@@ -2261,6 +2264,9 @@
   EXPECT_TRUE(CanStartAnimation("use"));
   EXPECT_FALSE(CanStartAnimation("use-offset"));
 
+  EXPECT_FALSE(CanStartAnimation("svg-zoomed"));
+  EXPECT_FALSE(CanStartAnimation("rect-zoomed"));
+
   To<SVGElement>(GetDocument().getElementById("rect"))
       ->SetWebAnimatedAttribute(
           svg_names::kXAttr,
diff --git a/third_party/blink/renderer/core/app_history/app_history_test.cc b/third_party/blink/renderer/core/app_history/app_history_test.cc
new file mode 100644
index 0000000..2046da7
--- /dev/null
+++ b/third_party/blink/renderer/core/app_history/app_history_test.cc
@@ -0,0 +1,54 @@
+// 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 "third_party/blink/renderer/core/app_history/app_history.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
+#include "third_party/blink/renderer/core/loader/frame_load_request.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
+
+namespace blink {
+
+class AppHistoryTest : public testing::Test {};
+
+class BeginNavigationClient : public frame_test_helpers::TestWebFrameClient {
+ public:
+  void BeginNavigation(
+      std::unique_ptr<blink::WebNavigationInfo> info) override {
+    begin_navigation_called_ = true;
+  }
+
+  bool BeginNavigationCalled() const { return begin_navigation_called_; }
+
+ private:
+  bool begin_navigation_called_ = false;
+};
+
+TEST_F(AppHistoryTest, NavigateEventCtrlClick) {
+  url_test_helpers::RegisterMockedURLLoad(
+      url_test_helpers::ToKURL(
+          "https://example.com/apphistory/onnavigate-preventDefault.html"),
+      test::CoreTestDataPath("apphistory/onnavigate-preventDefault.html"));
+
+  BeginNavigationClient client;
+  frame_test_helpers::WebViewHelper web_view_helper;
+  web_view_helper.InitializeAndLoad(
+      "https://example.com/apphistory/onnavigate-preventDefault.html", &client);
+  ASSERT_FALSE(client.BeginNavigationCalled());
+
+  // Emulate a navigation as started by a ctrl+click.
+  FrameLoadRequest request(nullptr, ResourceRequest(BlankURL()));
+  request.SetNavigationPolicy(kNavigationPolicyNewBackgroundTab);
+  web_view_helper.LocalMainFrame()->GetFrame()->Loader().StartNavigation(
+      request);
+
+  // The navigate event should not have fired for a ctrl+click.
+  // If the navigate event handler was executed, the navigation will have been
+  // cancelled, so check whether the begin navigation count was called.
+  EXPECT_TRUE(client.BeginNavigationCalled());
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index 924e11a7..a44e2cc 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -734,8 +734,7 @@
 
   STACK_UNINITIALIZED StyleCascade cascade(state);
 
-  ApplyBaseStyle(element, style_recalc_context, style_request, state, cascade,
-                 cascade.MutableMatchResult());
+  ApplyBaseStyle(element, style_recalc_context, style_request, state, cascade);
 
   if (style_request.IsPseudoStyleRequest() && state.HadNoMatchedProperties())
     return state.TakeStyle();
@@ -872,8 +871,7 @@
     const StyleRecalcContext& style_recalc_context,
     const StyleRequest& style_request,
     StyleResolverState& state,
-    StyleCascade& cascade,
-    MatchResult& match_result) {
+    StyleCascade& cascade) {
   DCHECK(style_request.pseudo_id != kPseudoIdFirstLineInherited);
 
   bool base_is_usable =
@@ -899,8 +897,9 @@
     }
 
     ElementRuleCollector collector(state.ElementContext(), style_recalc_context,
-                                   selector_filter_, match_result,
-                                   state.Style(), state.Style()->InsideLink());
+                                   selector_filter_,
+                                   cascade.MutableMatchResult(), state.Style(),
+                                   state.Style()->InsideLink());
 
     if (style_request.IsPseudoStyleRequest()) {
       collector.SetPseudoElementStyleRequest(style_request);
@@ -1485,8 +1484,7 @@
 
   // TODO(crbug.com/1145970): Use actual StyleRecalcContext.
   StyleRecalcContext style_recalc_context;
-  ApplyBaseStyle(&element, style_recalc_context, style_request, state, cascade,
-                 cascade.MutableMatchResult());
+  ApplyBaseStyle(&element, style_recalc_context, style_request, state, cascade);
   ApplyInterpolations(state, cascade, interpolations);
 
   return state.TakeStyle();
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.h b/third_party/blink/renderer/core/css/resolver/style_resolver.h
index 7ebfe1b..0db27de 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.h
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.h
@@ -161,8 +161,7 @@
                       const StyleRecalcContext&,
                       const StyleRequest&,
                       StyleResolverState& state,
-                      StyleCascade& cascade,
-                      MatchResult& match_result);
+                      StyleCascade& cascade);
   void ApplyInterpolations(StyleResolverState& state,
                            StyleCascade& cascade,
                            ActiveInterpolationsMap& interpolations);
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 368b7c7..df60552 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -261,6 +261,7 @@
 #include "third_party/blink/renderer/core/mathml/mathml_row_element.h"
 #include "third_party/blink/renderer/core/mathml_element_factory.h"
 #include "third_party/blink/renderer/core/mathml_names.h"
+#include "third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h"
 #include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/event_with_hit_test_results.h"
@@ -4041,6 +4042,8 @@
     return;
 
   GetFrame()->Loader().SaveScrollAnchor();
+  if (auto* mf_checker = View()->GetMobileFriendlinessChecker())
+    mf_checker->NotifyDocumentUnload();
 
   // TODO(crbug.com/1161996): Remove this VLOG once the investigation is done.
   VLOG(1) << "Actually dispatching an UnloadEvent: URL = " << Url();
@@ -8435,11 +8438,8 @@
   // TODO(bokan): Portals will change this assumption since they mean an active
   // document can be "adopted" into a portal.
   DCHECK(is_prerendering_);
+
   is_prerendering_ = false;
-
-  if (DocumentLoader* loader = Loader())
-    loader->NotifyPrerenderingDocumentActivated();
-
   DispatchEvent(*Event::Create(event_type_names::kPrerenderingchange));
 }
 
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index d898c98..cdca8529 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -3104,10 +3104,6 @@
     web_widget_->SetIsNestedMainFrameWidget(inside_portal);
 }
 
-void WebViewImpl::ActivatePrerender() {
-  GetPage()->ActivateForPrerendering();
-}
-
 void WebViewImpl::RegisterRendererPreferenceWatcher(
     CrossVariantMojoRemote<mojom::RendererPreferenceWatcherInterfaceBase>
         watcher) {
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index 9afdc9a5..79d0d2e 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -263,7 +263,6 @@
       SetPageLifecycleStateCallback callback) override;
   void AudioStateChanged(bool is_audio_playing) override;
   void SetInsidePortal(bool is_inside_portal) override;
-  void ActivatePrerender() override;
   void UpdateWebPreferences(
       const blink::web_pref::WebPreferences& preferences) override;
   void UpdateRendererPreferences(
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 180585a..97a507b 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -270,7 +270,9 @@
       layout_shift_tracker_(MakeGarbageCollected<LayoutShiftTracker>(this)),
       paint_timing_detector_(MakeGarbageCollected<PaintTimingDetector>(this)),
       mobile_friendliness_checker_(
-          MakeGarbageCollected<MobileFriendlinessChecker>(*this))
+          frame_->IsMainFrame()
+              ? MakeGarbageCollected<MobileFriendlinessChecker>(*this)
+              : nullptr)
 #if DCHECK_IS_ON()
       ,
       is_updating_descendant_dependent_flags_(false)
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index cdf9f79..d096c74 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -701,8 +701,8 @@
     return *paint_timing_detector_;
   }
 
-  MobileFriendlinessChecker& GetMobileFriendlinessChecker() const {
-    return *mobile_friendliness_checker_;
+  MobileFriendlinessChecker* GetMobileFriendlinessChecker() const {
+    return mobile_friendliness_checker_;
   }
   void DidChangeMobileFriendliness(const MobileFriendliness& mf);
 
@@ -1134,6 +1134,8 @@
   UniqueObjectId unique_id_;
   Member<LayoutShiftTracker> layout_shift_tracker_;
   Member<PaintTimingDetector> paint_timing_detector_;
+
+  // This will be nullptr iff !frame_->IsMainFrame().
   Member<MobileFriendlinessChecker> mobile_friendliness_checker_;
 
   HeapHashSet<WeakMember<LifecycleNotificationObserver>> lifecycle_observers_;
diff --git a/third_party/blink/renderer/core/frame/viewport_data.cc b/third_party/blink/renderer/core/frame/viewport_data.cc
index 135c7e3e..c551caa 100644
--- a/third_party/blink/renderer/core/frame/viewport_data.cc
+++ b/third_party/blink/renderer/core/frame/viewport_data.cc
@@ -106,7 +106,7 @@
   if (document_->GetFrame()->IsMainFrame()) {
     document_->GetPage()->GetChromeClient().DispatchViewportPropertiesDidChange(
         GetViewportDescription());
-    document_->View()->GetMobileFriendlinessChecker().NotifyViewportUpdated(
+    document_->View()->GetMobileFriendlinessChecker()->NotifyViewportUpdated(
         GetViewportDescription());
   }
 }
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
index 1e3ed7b..dab957a 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
@@ -189,10 +189,9 @@
       GetExecutionContext()->CanExecuteScripts(kNotAboutToExecuteScript)) {
     // Allocation of a layout object indicates that the canvas doesn't
     // have display:none set, so is conceptually being displayed.
-    if (context_) {
-      context_->SetIsBeingDisplayed(style.Visibility() ==
-                                    EVisibility::kVisible);
-    }
+    if (context_)
+      context_->SetIsBeingDisplayed(GetLayoutObject() && style_is_visible_);
+
     return new LayoutHTMLCanvas(this);
   }
   return HTMLElement::CreateLayoutObject(style, legacy);
@@ -368,11 +367,7 @@
 
   LayoutObject* layout_object = GetLayoutObject();
   if (layout_object) {
-    const ComputedStyle* style = GetComputedStyle();
-    if (style) {
-      context_->SetIsBeingDisplayed(style->Visibility() ==
-                                    EVisibility::kVisible);
-    }
+    context_->SetIsBeingDisplayed(GetLayoutObject() && style_is_visible_);
 
     if (IsRenderingContext2D() && !context_->CreationAttributes().alpha) {
       // In the alpha false case, canvas is initially opaque, so we need to
@@ -740,30 +735,12 @@
   return std::make_pair(broken_canvas_lo_res, 1);
 }
 
-static SkFilterQuality FilterQualityFromStyle(const ComputedStyle* style) {
-  if (style && style->ImageRendering() == EImageRendering::kPixelated)
-    return kNone_SkFilterQuality;
-  return kLow_SkFilterQuality;
-}
-
-SkFilterQuality HTMLCanvasElement::FilterQuality() const {
-  if (!isConnected())
-    return kLow_SkFilterQuality;
-
-  const ComputedStyle* style = GetComputedStyle();
-  if (!style) {
-    GetDocument().UpdateStyleAndLayoutTreeForNode(this);
-    HTMLCanvasElement* non_const_this = const_cast<HTMLCanvasElement*>(this);
-    style = non_const_this->EnsureComputedStyle();
-  }
-  return FilterQualityFromStyle(style);
-}
-
 bool HTMLCanvasElement::LowLatencyEnabled() const {
   return !!frame_dispatcher_;
 }
 
-void HTMLCanvasElement::UpdateFilterQuality(SkFilterQuality filter_quality) {
+void HTMLCanvasElement::SetFilterQuality(SkFilterQuality filter_quality) {
+  CanvasResourceHost::SetFilterQuality(filter_quality);
   if (IsOffscreenCanvasRegistered())
     UpdateOffscreenCanvasFilterQuality(filter_quality);
 
@@ -826,7 +803,6 @@
                                       const PhysicalRect& r) {
   context_->PaintRenderingResultsToCanvas(kFrontBuffer);
   if (HasResourceProvider()) {
-    const ComputedStyle* style = GetComputedStyle();
     // For 2D Canvas, there are two ways of render Canvas for printing:
     // display list or image snapshot. Display list allows better PDF printing
     // and we prefer this method.
@@ -840,7 +816,7 @@
     if (IsPrinting() && !Is3d() && canvas2d_bridge_) {
       canvas2d_bridge_->FlushRecording();
       if (canvas2d_bridge_->getLastRecord()) {
-        if (style && style->ImageRendering() != EImageRendering::kPixelated) {
+        if (FilterQuality() != kNone_SkFilterQuality) {
           context.Canvas()->save();
           context.Canvas()->translate(r.X(), r.Y());
           context.Canvas()->scale(r.Width() / Size().Width(),
@@ -867,6 +843,7 @@
       // GraphicsContext cannot handle gpu resource serialization.
       snapshot = snapshot->MakeUnaccelerated();
       DCHECK(!snapshot->IsTextureBacked());
+      const ComputedStyle* style = GetComputedStyle();
       context.DrawImage(snapshot.get(), Image::kSyncDecode,
                         FloatRect(PixelSnappedIntRect(r)), &src_rect,
                         style && style->HasFilterInducingProperty(),
@@ -1222,10 +1199,8 @@
     return;
 
   canvas2d_bridge_->SetCanvasResourceHost(this);
-  bool is_being_displayed =
-      GetLayoutObject() && GetComputedStyle() &&
-      GetComputedStyle()->Visibility() == EVisibility::kVisible;
-  canvas2d_bridge_->SetIsBeingDisplayed(is_being_displayed);
+
+  canvas2d_bridge_->SetIsBeingDisplayed(GetLayoutObject() && style_is_visible_);
 
   did_fail_to_create_resource_provider_ = false;
   UpdateMemoryUsage();
@@ -1305,9 +1280,15 @@
 
 void HTMLCanvasElement::StyleDidChange(const ComputedStyle* old_style,
                                        const ComputedStyle& new_style) {
-  UpdateFilterQuality(FilterQualityFromStyle(&new_style));
-  if (context_)
+  SkFilterQuality filter_quality = kLow_SkFilterQuality;
+  if (new_style.ImageRendering() == EImageRendering::kPixelated)
+    filter_quality = kNone_SkFilterQuality;
+  SetFilterQuality(filter_quality);
+  style_is_visible_ = new_style.Visibility() == EVisibility::kVisible;
+  if (context_) {
+    context_->SetIsBeingDisplayed(GetLayoutObject() && style_is_visible_);
     context_->StyleDidChange(old_style, new_style);
+  }
   if (StyleChangeNeedsDidDraw(old_style, new_style))
     DidDraw();
 }
@@ -1315,8 +1296,9 @@
 void HTMLCanvasElement::LayoutObjectDestroyed() {
   // If the canvas has no layout object then it definitely isn't being
   // displayed any more.
-  if (context_)
+  if (context_) {
     context_->SetIsBeingDisplayed(false);
+  }
 }
 
 void HTMLCanvasElement::DidMoveToNewDocument(Document& old_document) {
@@ -1716,6 +1698,12 @@
 }
 
 RespectImageOrientationEnum HTMLCanvasElement::RespectImageOrientation() const {
+  // TODO(junov): Computing style here will be problematic for applying the
+  // NoAllocDirectCall IDL attribute to drawImage.
+  if (!GetComputedStyle()) {
+    GetDocument().UpdateStyleAndLayoutTreeForNode(this);
+    const_cast<HTMLCanvasElement*>(this)->EnsureComputedStyle();
+  }
   return LayoutObject::ShouldRespectImageOrientation(GetLayoutObject());
 }
 
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.h b/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
index 79de50a..ac447f4 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
@@ -220,11 +220,11 @@
   void SetNeedsCompositingUpdate() override;
   void UpdateMemoryUsage() override;
   bool ShouldAccelerate2dContext() const override;
-  SkFilterQuality FilterQuality() const override;
   bool LowLatencyEnabled() const override;
   CanvasResourceProvider* GetOrCreateCanvasResourceProvider(
       RasterModeHint hint) override;
   bool IsPrinting() const override;
+  void SetFilterQuality(SkFilterQuality filter_quality) override;
 
   // CanvasRenderingContextHost implementation.
   UkmParameters GetUkmParameters() override;
@@ -338,7 +338,6 @@
       IdentifiableToken canvas_contents_token) const;
 
   void PaintInternal(GraphicsContext&, const PhysicalRect&);
-  void UpdateFilterQuality(SkFilterQuality filter_quality);
 
   using ContextFactoryVector =
       Vector<std::unique_ptr<CanvasRenderingContextFactory>>;
@@ -396,6 +395,7 @@
 
   bool origin_clean_;
   bool needs_unbuffered_input_ = false;
+  bool style_is_visible_ = false;
 
   // It prevents repeated attempts in allocating resources after the first
   // attempt failed.
diff --git a/third_party/blink/renderer/core/html/html_anchor_element.cc b/third_party/blink/renderer/core/html/html_anchor_element.cc
index 1eb0ea4..ffbd941 100644
--- a/third_party/blink/renderer/core/html/html_anchor_element.cc
+++ b/third_party/blink/renderer/core/html/html_anchor_element.cc
@@ -475,14 +475,16 @@
     // FindOrCreateFrameForNavigation() call, as that call may result in
     // performing a navigation if the call results in creating a new window with
     // noopener set.
+    // At this time we don't know if the navigation will navigate a main frame
+    // or subframe. For example, a middle click on the anchor element will
+    // set `target_frame` to `frame`, but end up targeting a new window.
+    // Attach the impression regardless, the embedder will be able to drop
+    // impressions for subframe navigations.
     base::Optional<WebImpression> impression = GetImpressionForAnchor(this);
     if (impression)
       frame_request.SetImpression(*impression);
   }
 
-  // Note that we do not need to worry about impressions being attached to
-  // subframe navigations in the following call, a frame is only
-  // created/navigated if we are intending to navigate a new window/main frame.
   Frame* target_frame =
       frame->Tree().FindOrCreateFrameForNavigation(frame_request, target).frame;
 
@@ -497,15 +499,8 @@
                       WebFeature::kHTMLAnchorElementHrefTranslateAttribute);
   }
 
-  if (target_frame) {
-    // We do not need to attach impressions for navigations to subframes, as the
-    // Conversion Measurement API only applies to main frame navigations. Clear
-    // out the impression in that case.
-    if (!target_frame->IsMainFrame()) {
-      frame_request.SetImpression(base::nullopt);
-    }
+  if (target_frame)
     target_frame->Navigate(frame_request, WebFrameLoadType::kStandard);
-  }
 }
 
 bool IsEnterKeyKeydownEvent(Event& event) {
diff --git a/third_party/blink/renderer/core/input/gesture_manager.cc b/third_party/blink/renderer/core/input/gesture_manager.cc
index 82f81db..ba6175ac 100644
--- a/third_party/blink/renderer/core/input/gesture_manager.cc
+++ b/third_party/blink/renderer/core/input/gesture_manager.cc
@@ -476,10 +476,9 @@
   const LocalFrameView::ScrollableAreaSet* areas = view->ScrollableAreas();
   if (!areas)
     return WebInputEventResult::kNotHandled;
-  for (const PaintLayerScrollableArea* scrollable_area : *areas) {
-    ScrollAnimatorBase* animator = scrollable_area->ExistingScrollAnimator();
-    if (scrollable_area->ScrollsOverflow() && animator)
-      animator->CancelAnimation();
+  for (PaintLayerScrollableArea* scrollable_area : *areas) {
+    if (scrollable_area->ScrollsOverflow())
+      scrollable_area->CancelScrollAnimation();
   }
   return WebInputEventResult::kNotHandled;
 }
diff --git a/third_party/blink/renderer/core/layout/layout-shift-tracker-old-paint-offset.md b/third_party/blink/renderer/core/layout/layout-shift-tracker-old-paint-offset.md
new file mode 100644
index 0000000..e454299
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/layout-shift-tracker-old-paint-offset.md
@@ -0,0 +1,77 @@
+# Explanation of `old_paint_offset` adjustment for
+`LayoutShiftTracker::NotifyBoxPrePaint()`
+
+Suppose from the layout shift root (see
+[PaintPropertyTreeBuilder](../paint/paint_property_tree_builder.h) for the
+definition) to a LayoutObject, there
+are transform nodes:
+```
+  {Troot, T1, T2, ... Tn}
+```
+where T1, T2, ... Tn are all 2d translations including
+* `PaintOffsetTranslation`s
+* `Transform`s with 2d translation matrixes
+* `ScrollTranslation`
+
+The location of the LayoutObject in the layout shift root can be calculated
+from:
+* the paint offset,
+* `acc_2d = sum(2d-offset(Ti), i=1..n)`
+
+We can calculate the old location and the new location in the layout shift
+root:
+```
+  old_location = old_paint_offset + old_acc_2d
+  new_location = new_paint_offset + new_acc_2d
+```
+
+`LayoutShiftTracker` then could use `old_location`, `new_location` to check
+if the object has shifted within the layout shift root. Then it could map
+`old_location` and `new_location` from the layout shift root's property tree
+state to the viewport's property tree state to get the old and new location
+in viewport, then check if the object has shifted in viewport. This would
+need `PaintInvalidator` to pass the following parameters to
+`LayoutShiftTracker`:
+* `old_location`
+* `new_location`
+* property tree state of the layout shift root [^1]
+
+[^1] Changes of paint properties above the layout shift root are ignored
+     intentionally because
+     * It's hard to track such changes.
+     * Some of such changes (e.g. 3d transform) should be ignored according to
+       the spec.
+     * Layout shift of the root itself should have already been reported, so
+       the descendants just need to report their shift relative to the layout
+       shift root.
+
+However, the above set of parameters requires `PaintInvalidator` to track
+all of them. To reduce the amount of data to track, `PaintInvalidator` passes
+the following parameters instead [^2]:
+* `adjusted_old_paint_offset = old_paint_offset - acc_2d_delta`
+* `new_paint_offset`
+* property tree state of the current object
+most of which can be gotten from the current context except `acc_2d_delta`.
+Then `LayoutShiftTracker` can use `adjusted_old_paint_offset` and
+`new_paint_offset` instead of `old_location` and `new_location` to check if
+the object has shifted within the layout shift root because
+```
+  new_location - old_location == new_paint_offset - adjusted_old_paint_offset
+```
+and it can map `adjusted_old_paint_offset` and `new_paint_offset` from the
+current paint property tree state of the object to the viewport, as if there
+were old paint property tree state below the layout shift root because the
+changes of paint properties below the layout shift root is accounted in
+`adjusted_old_paint_offset`.
+
+[^2] Actually `PaintInvalidator` also passes the following parameters:
+     * `translation_delta`
+     * `scroll_delta`
+     so that `LayoutShiftTracker` can check shift by ignoring (or not) 2d
+     translation and scroll changes below the layout shift root.
+     See [the explainer](https://github.com/WICG/layout-instability#transform-changes)
+     for why we ignore transform and scroll changes by default. However,
+     in case that a layout shift is countered by a transform change and/or a
+     scroll change making the element not visually move, we should ignore the
+     shift. These situations require `LayoutShiftTracker` to determine shift
+     by both including and not including the transform/scroll changes.
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow_line.cc b/third_party/blink/renderer/core/layout/layout_block_flow_line.cc
index aa222f83..c773c75 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow_line.cc
+++ b/third_party/blink/renderer/core/layout/layout_block_flow_line.cc
@@ -2197,6 +2197,10 @@
 
     // If we have no dirty lines, then last is just the last root box.
     last = curr ? curr->PrevRootBox() : LastRootBox();
+#if DCHECK_IS_ON()
+    if (last && last->LineBreakObj().GetLayoutObject())
+      last->LineBreakObj().GetLayoutObject()->CheckIsNotDestroyed();
+#endif
   }
 
   unsigned num_clean_floats = 0;
diff --git a/third_party/blink/renderer/core/layout/layout_box_model_object.cc b/third_party/blink/renderer/core/layout/layout_box_model_object.cc
index 8712143..ce894f5 100644
--- a/third_party/blink/renderer/core/layout/layout_box_model_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_box_model_object.cc
@@ -1438,15 +1438,9 @@
   }
 
   PhysicalOffset container_offset = OffsetFromContainer(container);
-  bool offset_depends_on_point;
-  if (IsLayoutFlowThread()) {
+  bool offset_depends_on_point = OffsetForContainerDependsOnPoint(container);
+  if (offset_depends_on_point && IsLayoutFlowThread())
     container_offset += PhysicalOffsetToBeNoop(ColumnOffset(LayoutPoint()));
-    offset_depends_on_point = true;
-  } else {
-    offset_depends_on_point =
-        container->StyleRef().IsFlippedBlocksWritingMode() &&
-        container->IsBox();
-  }
 
   bool preserve3d =
       container->StyleRef().Preserves3D() || StyleRef().Preserves3D();
diff --git a/third_party/blink/renderer/core/layout/layout_inline.cc b/third_party/blink/renderer/core/layout/layout_inline.cc
index 94cfb6b..c3d5701 100644
--- a/third_party/blink/renderer/core/layout/layout_inline.cc
+++ b/third_party/blink/renderer/core/layout/layout_inline.cc
@@ -32,7 +32,6 @@
 #include "third_party/blink/renderer/core/layout/geometry/transform_state.h"
 #include "third_party/blink/renderer/core/layout/hit_test_result.h"
 #include "third_party/blink/renderer/core/layout/layout_block.h"
-#include "third_party/blink/renderer/core/layout/layout_geometry_map.h"
 #include "third_party/blink/renderer/core/layout/layout_theme.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/core/layout/line/inline_text_box.h"
@@ -944,6 +943,19 @@
   }
 }
 
+bool LayoutInline::AbsoluteTransformDependsOnPoint(
+    const LayoutObject& object) const {
+  const LayoutObject* current = &object;
+  const LayoutObject* container = object.Container();
+  while (container) {
+    if (current->OffsetForContainerDependsOnPoint(container))
+      return true;
+    current = container;
+    container = container->Container();
+  }
+  return false;
+}
+
 void LayoutInline::LocalQuadsForSelf(Vector<FloatQuad>& quads) const {
   QuadsForSelfInternal(quads, 0, false);
 }
@@ -957,20 +969,39 @@
                                         MapCoordinatesFlags mode,
                                         bool map_to_absolute) const {
   NOT_DESTROYED();
-  LayoutGeometryMap geometry_map(mode);
-  geometry_map.PushMappingsToAncestor(this, nullptr);
+  base::Optional<TransformationMatrix> mapping_to_absolute;
+  // Set to true if the transform to absolute space depends on the point
+  // being mapped (in which case we can't use LocalToAbsoluteTransform).
+  bool transform_depends_on_point = false;
+  bool transform_depends_on_point_computed = false;
+  auto PushAbsoluteQuad = [&transform_depends_on_point,
+                           &transform_depends_on_point_computed,
+                           &mapping_to_absolute, &quads, mode,
+                           this](const PhysicalRect& rect) {
+    if (!transform_depends_on_point_computed) {
+      transform_depends_on_point_computed = true;
+      transform_depends_on_point = AbsoluteTransformDependsOnPoint(*this);
+      if (!transform_depends_on_point)
+        mapping_to_absolute.emplace(LocalToAbsoluteTransform(mode));
+    }
+    if (transform_depends_on_point)
+      quads.push_back(LocalToAbsoluteQuad(FloatQuad(FloatRect(rect)), mode));
+    else
+      quads.push_back(mapping_to_absolute->MapQuad(FloatQuad(FloatRect(rect))));
+  };
+
   CollectLineBoxRects(
-      [&quads, &geometry_map, &map_to_absolute](const PhysicalRect& r) {
+      [&PushAbsoluteQuad, &map_to_absolute, &quads](const PhysicalRect& rect) {
         if (map_to_absolute)
-          quads.push_back(geometry_map.AbsoluteQuad(r));
+          PushAbsoluteQuad(rect);
         else
-          quads.push_back(FloatQuad(FloatRect(r)));
+          quads.push_back(FloatQuad(FloatRect(rect)));
       });
   if (quads.IsEmpty()) {
     if (map_to_absolute)
-      quads.push_back(geometry_map.AbsoluteQuad(PhysicalRect()));
+      PushAbsoluteQuad(PhysicalRect());
     else
-      quads.push_back(FloatQuad(FloatRect()));
+      quads.push_back(FloatQuad());
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_inline.h b/third_party/blink/renderer/core/layout/layout_inline.h
index 0951e3b..31f0ae69 100644
--- a/third_party/blink/renderer/core/layout/layout_inline.h
+++ b/third_party/blink/renderer/core/layout/layout_inline.h
@@ -294,6 +294,7 @@
       bool ignore_scroll_offset) const final;
 
  private:
+  bool AbsoluteTransformDependsOnPoint(const LayoutObject& object) const;
   void QuadsForSelfInternal(Vector<FloatQuad>& quads,
                             MapCoordinatesFlags mode,
                             bool map_to_absolute) const;
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 527b31d1..874a9ed 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -3355,6 +3355,13 @@
   return transform_state.AccumulatedTransform();
 }
 
+bool LayoutObject::OffsetForContainerDependsOnPoint(
+    const LayoutObject* container) const {
+  return IsLayoutFlowThread() ||
+         (container->StyleRef().IsFlippedBlocksWritingMode() &&
+          container->IsBox());
+}
+
 PhysicalOffset LayoutObject::OffsetFromContainer(
     const LayoutObject* o,
     bool ignore_scroll_offset) const {
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index ab7db4d..3e0b958b 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -323,6 +323,10 @@
   // Returns false iff this object or one of its ancestors has opacity:0.
   bool HasNonZeroEffectiveOpacity() const;
 
+  // Returns true if the offset ot the containing block depends on the point
+  // being mapped.
+  bool OffsetForContainerDependsOnPoint(const LayoutObject* container) const;
+
  protected:
   void EnsureIdForTesting() {
     NOT_DESTROYED();
diff --git a/third_party/blink/renderer/core/layout/layout_shift_tracker.cc b/third_party/blink/renderer/core/layout/layout_shift_tracker.cc
index 4d82951..fa19422 100644
--- a/third_party/blink/renderer/core/layout/layout_shift_tracker.cc
+++ b/third_party/blink/renderer/core/layout/layout_shift_tracker.cc
@@ -205,7 +205,8 @@
     const PhysicalRect& old_rect,
     const PhysicalRect& new_rect,
     const FloatPoint& old_starting_point,
-    const FloatPoint& old_transform_indifferent_starting_point,
+    const FloatSize& translation_delta,
+    const FloatSize& scroll_delta,
     const FloatPoint& new_starting_point) {
   // The caller should ensure these conditions.
   DCHECK(!old_rect.IsEmpty());
@@ -214,15 +215,31 @@
   float threshold_physical_px =
       kMovementThreshold * object.StyleRef().EffectiveZoom();
 
+  // Check shift of starting point, including 2d-translation and scroll deltas.
   if (EqualWithinMovementThreshold(old_starting_point, new_starting_point,
                                    threshold_physical_px))
     return;
 
-  if (old_transform_indifferent_starting_point != old_starting_point &&
-      EqualWithinMovementThreshold(old_transform_indifferent_starting_point,
+  // Check shift of 2d-translation-indifferent starting point.
+  if (!translation_delta.IsZero() &&
+      EqualWithinMovementThreshold(old_starting_point + translation_delta,
                                    new_starting_point, threshold_physical_px))
     return;
 
+  // Check shift of scroll-indifferent starting point.
+  if (!scroll_delta.IsZero() &&
+      EqualWithinMovementThreshold(old_starting_point + scroll_delta,
+                                   new_starting_point, threshold_physical_px))
+    return;
+
+  // Check shift of 2d-translation-and-scroll-indifferent starting point.
+  FloatSize translation_and_scroll_delta = scroll_delta + translation_delta;
+  if (!translation_and_scroll_delta.IsZero() &&
+      EqualWithinMovementThreshold(
+          old_starting_point + translation_and_scroll_delta, new_starting_point,
+          threshold_physical_px))
+    return;
+
   if (SmallerThanRegionGranularity(old_rect) &&
       SmallerThanRegionGranularity(new_rect))
     return;
@@ -254,25 +271,35 @@
                                    threshold_physical_px))
     return;
 
-  if (EqualWithinMovementThreshold(
-          old_starting_point_in_root + frame_scroll_delta_,
-          new_starting_point_in_root, threshold_physical_px)) {
-    // TODO(skobes): Checking frame_scroll_delta_ is an imperfect solution to
-    // allowing counterscrolled layout shifts. Ideally, we would map old_rect
-    // to viewport coordinates using the previous frame's scroll tree.
-    return;
-  }
-
-  if (old_transform_indifferent_starting_point != old_starting_point) {
-    FloatPoint old_transform_indifferent_starting_point_in_root =
-        transform.MapPoint(old_transform_indifferent_starting_point);
+  FloatPoint old_transform_indifferent_starting_point_in_root =
+      old_starting_point_in_root;
+  if (!translation_delta.IsZero()) {
+    old_transform_indifferent_starting_point_in_root =
+        transform.MapPoint(old_starting_point + translation_delta);
     if (EqualWithinMovementThreshold(
             old_transform_indifferent_starting_point_in_root,
             new_starting_point_in_root, threshold_physical_px))
       return;
+  }
+
+  FloatPoint old_scroll_indifferent_starting_point_in_root =
+      old_starting_point_in_root;
+  if (!scroll_delta.IsZero()) {
+    old_scroll_indifferent_starting_point_in_root =
+        transform.MapPoint(old_starting_point + scroll_delta);
     if (EqualWithinMovementThreshold(
-            old_transform_indifferent_starting_point_in_root +
-                frame_scroll_delta_,
+            old_scroll_indifferent_starting_point_in_root,
+            new_starting_point_in_root, threshold_physical_px))
+      return;
+  }
+
+  FloatPoint old_transform_and_scroll_indifferent_starting_point_in_root =
+      old_starting_point_in_root;
+  if (!translation_and_scroll_delta.IsZero()) {
+    old_transform_and_scroll_indifferent_starting_point_in_root =
+        transform.MapPoint(old_starting_point + translation_and_scroll_delta);
+    if (EqualWithinMovementThreshold(
+            old_transform_and_scroll_indifferent_starting_point_in_root,
             new_starting_point_in_root, threshold_physical_px))
       return;
   }
@@ -303,8 +330,20 @@
             << " (visible from " << visible_old_rect << " to "
             << visible_new_rect << ")";
     if (old_starting_point_in_root != old_rect_in_root.Location() ||
-        new_starting_point_in_root != new_rect_in_root.Location()) {
+        new_starting_point_in_root != new_rect_in_root.Location() ||
+        !translation_delta.IsZero() || !scroll_delta.IsZero()) {
+#define LOG_OLD_STARTING_POINT(condition, name)                               \
+  ((condition)                                                                \
+       ? " (" #name "_indifferent: " +                                        \
+             old_##name##_indifferent_starting_point_in_root.ToString() + ")" \
+       : String())                                                            \
+      .Utf8()
       VLOG(1) << " (starting point from " << old_starting_point_in_root
+              << LOG_OLD_STARTING_POINT(!translation_delta.IsZero(), transform)
+              << LOG_OLD_STARTING_POINT(!scroll_delta.IsZero(), scroll)
+              << LOG_OLD_STARTING_POINT(
+                     !translation_delta.IsZero() && !scroll_delta.IsZero(),
+                     transform_and_scroll)
               << " to " << new_starting_point_in_root << ")";
     }
   }
@@ -373,13 +412,13 @@
     const PhysicalRect& old_rect,
     const PhysicalRect& new_rect,
     const PhysicalOffset& old_paint_offset,
-    const PhysicalOffset& old_transform_indifferent_paint_offset,
+    const FloatSize& translation_delta,
+    const FloatSize& scroll_delta,
     const PhysicalOffset& new_paint_offset) {
   DCHECK(NeedsToTrack(box));
   ObjectShifted(box, property_tree_state, old_rect, new_rect,
                 StartingPoint(old_paint_offset, box, box.PreviousSize()),
-                StartingPoint(old_transform_indifferent_paint_offset, box,
-                              box.PreviousSize()),
+                translation_delta, scroll_delta,
                 StartingPoint(new_paint_offset, box, box.Size()));
 }
 
@@ -389,7 +428,8 @@
     const LogicalOffset& old_starting_point,
     const LogicalOffset& new_starting_point,
     const PhysicalOffset& old_paint_offset,
-    const PhysicalOffset& old_transform_indifferent_paint_offset,
+    const FloatSize& translation_delta,
+    const FloatSize& scroll_delta,
     const PhysicalOffset& new_paint_offset,
     LayoutUnit logical_height) {
   DCHECK(NeedsToTrack(text));
@@ -401,9 +441,6 @@
       old_paint_offset + old_starting_point.ConvertToPhysical(writing_direction,
                                                               block->old_size_,
                                                               PhysicalSize());
-  PhysicalOffset old_transform_indifferent_physical_starting_point =
-      old_physical_starting_point + old_transform_indifferent_paint_offset -
-      old_paint_offset;
   PhysicalOffset new_physical_starting_point =
       new_paint_offset + new_starting_point.ConvertToPhysical(writing_direction,
                                                               block->new_size_,
@@ -421,9 +458,8 @@
     return;
 
   ObjectShifted(text, property_tree_state, old_rect, new_rect,
-                FloatPoint(old_physical_starting_point),
-                FloatPoint(old_transform_indifferent_physical_starting_point),
-                FloatPoint(new_physical_starting_point));
+                FloatPoint(old_physical_starting_point), translation_delta,
+                scroll_delta, FloatPoint(new_physical_starting_point));
 }
 
 double LayoutShiftTracker::SubframeWeightingFactor() const {
@@ -501,7 +537,6 @@
   // Reset accumulated state.
   region_.Reset();
   frame_max_distance_ = 0.0;
-  frame_scroll_delta_ = ScrollOffset();
   attributions_.fill(Attribution());
 }
 
@@ -615,10 +650,7 @@
   }
 }
 
-void LayoutShiftTracker::NotifyScroll(mojom::blink::ScrollType scroll_type,
-                                      ScrollOffset delta) {
-  frame_scroll_delta_ += delta;
-
+void LayoutShiftTracker::NotifyScroll(mojom::blink::ScrollType scroll_type) {
   // Only set observed_input_or_scroll_ for user-initiated scrolls, and not
   // other scrolls such as hash fragment navigations.
   if (scroll_type == mojom::blink::ScrollType::kUser ||
diff --git a/third_party/blink/renderer/core/layout/layout_shift_tracker.h b/third_party/blink/renderer/core/layout/layout_shift_tracker.h
index c0cc8c0..4da59b4 100644
--- a/third_party/blink/renderer/core/layout/layout_shift_tracker.h
+++ b/third_party/blink/renderer/core/layout/layout_shift_tracker.h
@@ -46,30 +46,34 @@
   // |old_rect| and |old_paint_offset| so that we can calculate the correct old
   // visual representation and old starting point in the initial containing
   // block and the viewport with the new property tree state in most cases.
-  // |old_transform_indifferent_paint_offset| is the adjusted old paint offset
-  // with transform changes excluded.
-  void NotifyBoxPrePaint(
-      const LayoutBox& box,
-      const PropertyTreeStateOrAlias& property_tree_state,
-      const PhysicalRect& old_rect,
-      const PhysicalRect& new_rect,
-      const PhysicalOffset& old_paint_offset,
-      const PhysicalOffset& old_transform_indifferent_paint_offset,
-      const PhysicalOffset& new_paint_offset);
+  // The adjustment should include the deltas of 2d translations and scrolls,
+  // and LayoutShiftTracker can determine stability by including (by default)
+  // or excluding |translation_delta| and/or |scroll_delta|.
+  //
+  // See renderer/core/layout/layout-shift-tracker-old-paint-offset.md for
+  // more details about |old_paint_offset|.
+  void NotifyBoxPrePaint(const LayoutBox& box,
+                         const PropertyTreeStateOrAlias& property_tree_state,
+                         const PhysicalRect& old_rect,
+                         const PhysicalRect& new_rect,
+                         const PhysicalOffset& old_paint_offset,
+                         const FloatSize& translation_delta,
+                         const FloatSize& scroll_delta,
+                         const PhysicalOffset& new_paint_offset);
 
-  void NotifyTextPrePaint(
-      const LayoutText& text,
-      const PropertyTreeStateOrAlias& property_tree_state,
-      const LogicalOffset& old_starting_point,
-      const LogicalOffset& new_starting_point,
-      const PhysicalOffset& old_paint_offset,
-      const PhysicalOffset& old_transform_indifferent_paint_offset,
-      const PhysicalOffset& new_paint_offset,
-      const LayoutUnit logical_height);
+  void NotifyTextPrePaint(const LayoutText& text,
+                          const PropertyTreeStateOrAlias& property_tree_state,
+                          const LogicalOffset& old_starting_point,
+                          const LogicalOffset& new_starting_point,
+                          const PhysicalOffset& old_paint_offset,
+                          const FloatSize& translation_delta,
+                          const FloatSize& scroll_delta,
+                          const PhysicalOffset& new_paint_offset,
+                          const LayoutUnit logical_height);
 
   void NotifyPrePaintFinished();
   void NotifyInput(const WebInputEvent&);
-  void NotifyScroll(mojom::blink::ScrollType, ScrollOffset delta);
+  void NotifyScroll(mojom::blink::ScrollType);
   void NotifyViewportSizeChanged();
   void NotifyFindInPageInput();
   void NotifyChangeEvent();
@@ -153,7 +157,8 @@
                      const PhysicalRect& old_rect,
                      const PhysicalRect& new_rect,
                      const FloatPoint& old_starting_point,
-                     const FloatPoint& old_transform_indifferent_starting_point,
+                     const FloatSize& translation_delta,
+                     const FloatSize& scroll_offset_delta,
                      const FloatPoint& new_starting_point);
 
   void ReportShift(double score_delta, double weighted_score_delta);
@@ -216,9 +221,6 @@
   // frames.
   float overall_max_distance_;
 
-  // Sum of all scroll deltas that occurred in the current animation frame.
-  ScrollOffset frame_scroll_delta_;
-
   // Whether either a user input or document scroll have been observed during
   // the session. (This is only tracked so UkmPageLoadMetricsObserver to report
   // LayoutInstability.CumulativeShiftScore.MainFrame.BeforeInputOrScroll. It's
diff --git a/third_party/blink/renderer/core/layout/line/line_box_list.cc b/third_party/blink/renderer/core/layout/line/line_box_list.cc
index 9d9d861f..ed92984 100644
--- a/third_party/blink/renderer/core/layout/line/line_box_list.cc
+++ b/third_party/blink/renderer/core/layout/line/line_box_list.cc
@@ -359,14 +359,32 @@
     // findNextLineBreak. findNextLineBreak, despite the name, actually returns
     // the first LayoutObject after the BR. <rdar://problem/3849947> "Typing
     // after pasting line does not appear until after window resize."
-    if (RootInlineBox* prev_root_box = box->PrevRootBox())
+    if (RootInlineBox* prev_root_box = box->PrevRootBox()) {
       prev_root_box->MarkDirty();
+#if DCHECK_IS_ON()
+      for (; prev_root_box; prev_root_box = prev_root_box->PrevRootBox()) {
+        DCHECK(prev_root_box->IsDirty() ||
+               prev_root_box->LineBreakObj() != child);
+      }
+#endif
+    }
     // If |child| or any of its immediately previous siblings with culled
     // lineboxes is the object after a line-break in |box| or the linebox after
     // it then that means |child| actually sits on the linebox after |box| (or
     // is its line-break object) and so we need to dirty it as well.
-    if (RootInlineBox* next_root_box = box->NextRootBox())
+    if (RootInlineBox* next_root_box = box->NextRootBox()) {
       next_root_box->MarkDirty();
+
+      next_root_box = next_root_box->NextRootBox();
+      if (next_root_box && next_root_box->LineBreakObj() == child)
+        next_root_box->MarkDirty();
+#if DCHECK_IS_ON()
+      for (; next_root_box; next_root_box = next_root_box->NextRootBox()) {
+        DCHECK(next_root_box->IsDirty() ||
+               next_root_box->LineBreakObj() != child);
+      }
+#endif
+    }
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index 1c80c07..510e7331 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -1230,6 +1230,7 @@
   const NGConstraintSpace& space = GetFragmentainerConstraintSpace(index);
 
   // If we are a new fragment, find a non-spanner fragmentainer as a basis.
+  wtf_size_t original_index = index;
   while (
       index >= num_children ||
       !container_builder_->Children()[index].fragment->IsFragmentainerBox()) {
@@ -1243,19 +1244,13 @@
   const auto& fragment =
       To<NGPhysicalBoxFragment>(*fragmentainer.fragment.get());
 
-  // Grab the previous fragment's break token. We can't just use the current
-  // fragment's break token sequence, since there might not be one if we're
-  // adding a new fragment.
-  const NGBreakToken* previous_break_token =
-      (index == 0)
-          ? nullptr
-          : To<NGPhysicalBoxFragment>(
-                container_builder_->Children()[index - 1].fragment.get())
-                ->BreakToken();
+  const NGBlockBreakToken* previous_break_token =
+      PreviousFragmentainerBreakToken(add_to_last_fragment ? index
+                                                           : original_index);
   NGFragmentGeometry fragment_geometry =
       CalculateInitialFragmentGeometry(space, node);
   NGLayoutAlgorithmParams params(node, fragment_geometry, space,
-                                 To<NGBlockBreakToken>(previous_break_token),
+                                 previous_break_token,
                                  /* early_break */ nullptr);
 
   // |algorithm| corresponds to the "mutable copy" of our original
@@ -1440,6 +1435,21 @@
       /* balance_columns */ false);
 }
 
+const NGBlockBreakToken* NGOutOfFlowLayoutPart::PreviousFragmentainerBreakToken(
+    wtf_size_t index) const {
+  const NGBlockBreakToken* previous_break_token = nullptr;
+  for (wtf_size_t i = index; i > 0; --i) {
+    auto* previous_fragment =
+        container_builder_->Children()[i - 1].fragment.get();
+    if (previous_fragment->IsFragmentainerBox()) {
+      previous_break_token = To<NGBlockBreakToken>(
+          To<NGPhysicalBoxFragment>(previous_fragment)->BreakToken());
+      break;
+    }
+  }
+  return previous_break_token;
+}
+
 // Compute in which fragmentainer the OOF element will start its layout and
 // position the offset relative to that fragmentainer.
 void NGOutOfFlowLayoutPart::ComputeStartFragmentIndexAndRelativeOffset(
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
index 4789308..0382b08 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
@@ -235,6 +235,8 @@
                             NGSimplifiedOOFLayoutAlgorithm* algorithm,
                             Vector<MulticolChildInfo>* multicol_children);
   NGConstraintSpace GetFragmentainerConstraintSpace(wtf_size_t index);
+  const NGBlockBreakToken* PreviousFragmentainerBreakToken(
+      wtf_size_t index) const;
   void ComputeStartFragmentIndexAndRelativeOffset(
       const ContainingBlockInfo& container_info,
       WritingMode default_writing_mode,
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part_test.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part_test.cc
index 7418bb5..9f05260 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part_test.cc
@@ -1403,13 +1403,9 @@
   EXPECT_EQ(expectation, dump);
 }
 
-// TODO(almaher): Figure out why this is hitting a DCHECK in
-// AssertClearedPaintInvalidationFlags.
-//
 // Fragmented OOF element inside a nested multi-column starting at a
 // fragmentainer index beyond the last existing fragmentainer.
-TEST_F(NGOutOfFlowLayoutPartTest,
-       DISABLED_AbsposNestedFragmentationNewEmptyColumns) {
+TEST_F(NGOutOfFlowLayoutPartTest, AbsposNestedFragmentationNewEmptyColumns) {
   SetBodyInnerHTML(
       R"HTML(
       <style>
@@ -1439,8 +1435,6 @@
       )HTML");
   String dump = DumpFragmentTree(GetElementById("container"));
 
-  // TODO(almaher): The OOFs are added out of order due to how ordering is
-  // currently handled in NGSimplifiedOOFLayoutAlgorithm.
   String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::.
   offset:unplaced size:1000x100
     offset:0,0 size:1000x100
@@ -1451,9 +1445,9 @@
             offset:0,0 size:55x40
           offset:258,0 size:242x40
             offset:0,0 size:55x40
+            offset:516,0 size:5x40
             offset:774,0 size:5x40
             offset:1032,0 size:5x40
-            offset:516,0 size:5x40
           offset:0,40 size:500x0
           offset:0,40 size:500x0
           offset:0,40 size:500x0
diff --git a/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc
index 4c0508e..b720fdab 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc
@@ -18,7 +18,8 @@
     const NGPhysicalBoxFragment& fragment,
     bool is_new_fragment)
     : NGLayoutAlgorithm(params),
-      writing_direction_(Style().GetWritingDirection()) {
+      writing_direction_(Style().GetWritingDirection()),
+      incoming_break_token_(params.break_token) {
   DCHECK(fragment.IsFragmentainerBox());
   DCHECK(params.space.HasKnownFragmentainerBlockSize());
 
@@ -26,10 +27,12 @@
   container_builder_.SetFragmentBlockSize(
       params.space.FragmentainerBlockSize());
 
+  if (incoming_break_token_)
+    break_token_iterator_ = incoming_break_token_->ChildBreakTokens().begin();
+
   // Don't apply children to new fragments.
   if (is_new_fragment) {
-    children_ = {};
-    iterator_ = children_.end();
+    child_iterator_ = children_.end();
     container_builder_.SetIsFirstForNode(false);
     return;
   }
@@ -40,78 +43,38 @@
   // any child fragments.
   previous_physical_container_size_ = fragment.Size();
 
-  // The OOF fragments need to be added in a particular order that matches the
-  // order of break tokens. Here's a list of rules to follow , in order:
-  // 1. If there are any already appended fragments that are a continuation of
-  //    layout (i.e. they are the Nth fragment of a layout box, where N > 1),
-  //    they should be appended before anything else.
-  // 2. If we're trying to append a fragment that is a continuation of layout
-  //    for an OOF node (from AppendOutOfFlowResult), add it after the fragments
-  //    added in step 1.
-  // 3. Add the remaining children that were not appended during step 1.
-  // 4. Add the OOF fragments that were not a continuation of layout, the ones
-  //    that weren't appended in step 2.
+  // Children (along with any OOF fragments that will be added as children) need
+  // to be added in an order that matches the order of any incoming break tokens
+  // (as indicated by the order in |break_token_iterator_|). After all incoming
+  // break tokens are accounted for, the order will be determined by the
+  // remaining children in |child_iterator_|, followed by any newly added OOF
+  // children.
   children_ = fragment.Children();
-  iterator_ = children_.begin();
-
-  while (iterator_ != children_.end()) {
-    const auto& child_link = *iterator_;
-    const auto* child_fragment = To<NGPhysicalBoxFragment>(child_link.get());
-    if (!child_fragment->IsFirstForNode()) {
-      AddChildFragment(child_link);
-      iterator_++;
-    } else {
-      // We can break here because fragments that are a continuation of layout
-      // are always the first children of a physical fragment.
-      break;
-    }
-  }
+  child_iterator_ = children_.begin();
+  AdvanceChildIterator();
 }
 
 scoped_refptr<const NGLayoutResult> NGSimplifiedOOFLayoutAlgorithm::Layout() {
-  // There might not be any children to append, whether it's because we are in a
-  // new fragmentainer or because they have all been added in Step 1.
-  if (iterator_ != children_.end()) {
-    // Step 3: Add the remaining children that were not added in step 1.
-    while (iterator_ != children_.end()) {
-      const auto& child_link = *iterator_;
-      const auto* child_fragment = To<NGPhysicalBoxFragment>(child_link.get());
-      DCHECK(child_fragment->IsFirstForNode());
-      AddChildFragment(child_link);
-      iterator_++;
-    }
-
-    // Step 4: Add the OOF fragments that aren't a continuation of layout.
-    for (auto result : remaining_oof_results_) {
-      container_builder_.AddResult(*result,
-                                   result->OutOfFlowPositionedOffset());
-    }
-  }
   return container_builder_.ToBoxFragment();
 }
 
 void NGSimplifiedOOFLayoutAlgorithm::AppendOutOfFlowResult(
     scoped_refptr<const NGLayoutResult> result) {
-  // Add the new result directly to the builder when the fragment of the result
-  // to append is not the first fragment of its corresponding layout box,
-  // meaning that it's positioned directly at the start of the fragmentainer.
-  // This ensures that we keep the fragments and the break tokens in order.
-  //
-  // Also add the result directly to the builder if there are no more children
-  // to append. This can happen when all children have been added in Step 1 or
-  // when we are in a new fragmentainer since a new fragmentainer doesn't have
-  // any child.
-  if (iterator_ == children_.end() ||
-      !To<NGPhysicalBoxFragment>(result->PhysicalFragment()).IsFirstForNode()) {
-    // Step 2: Add the fragments that are a continuation of layout directly to
-    // the builder.
-    container_builder_.AddResult(*result, result->OutOfFlowPositionedOffset());
-    return;
+  container_builder_.AddResult(*result, result->OutOfFlowPositionedOffset());
+
+  // If there is an incoming child break token, make sure that it matches
+  // the OOF child that was just added.
+  if (incoming_break_token_ &&
+      break_token_iterator_ !=
+          incoming_break_token_->ChildBreakTokens().end()) {
+    DCHECK_EQ(result->PhysicalFragment().GetLayoutObject(),
+              (*break_token_iterator_)->InputNode().GetLayoutBox());
+    DCHECK(!To<NGPhysicalBoxFragment>(result->PhysicalFragment())
+                .IsFirstForNode() ||
+           To<NGBlockBreakToken>(*break_token_iterator_)->IsBreakBefore());
+    break_token_iterator_++;
+    AdvanceChildIterator();
   }
-  // Since there is no previous break token associated with the first fragment
-  // of a fragmented OOF element, we cannot append this result before any other
-  // children of this fragmentainer. Keep the order by adding it after.
-  remaining_oof_results_.push_back(result);
 }
 
 void NGSimplifiedOOFLayoutAlgorithm::AddChildFragment(const NGLink& child) {
@@ -128,4 +91,35 @@
   container_builder_.AddChild(*fragment, child_offset);
 }
 
+void NGSimplifiedOOFLayoutAlgorithm::AdvanceChildIterator() {
+  while (child_iterator_ != children_.end()) {
+    const auto& child_link = *child_iterator_;
+    if (incoming_break_token_ &&
+        break_token_iterator_ !=
+            incoming_break_token_->ChildBreakTokens().end()) {
+      // Add the current child if it matches the incoming child break token.
+      const auto* break_token = *break_token_iterator_;
+      if (child_link.fragment->GetLayoutObject() ==
+          break_token->InputNode().GetLayoutBox()) {
+        DCHECK(!To<NGPhysicalBoxFragment>(child_link.get())->IsFirstForNode() ||
+               To<NGBlockBreakToken>(break_token)->IsBreakBefore());
+        AddChildFragment(child_link);
+        child_iterator_++;
+        break_token_iterator_++;
+      } else {
+        // The current child does not match the incoming break token. The break
+        // token must belong to an OOF positioned element that has not yet been
+        // added via AppendOutOfFlowResult().
+        DCHECK(break_token->InputNode().IsOutOfFlowPositioned());
+        break;
+      }
+    } else {
+      // There are no more incoming child break tokens, so add the remaining
+      // children in |child_iterator_|.
+      AddChildFragment(child_link);
+      child_iterator_++;
+    }
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.h
index fefde89..51c32d3 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.h
@@ -38,13 +38,15 @@
 
  private:
   void AddChildFragment(const NGLink& old_fragment);
+  void AdvanceChildIterator();
 
   const WritingDirectionMode writing_direction_;
   PhysicalSize previous_physical_container_size_;
 
-  Vector<scoped_refptr<const NGLayoutResult>> remaining_oof_results_;
   base::span<const NGLink> children_;
-  base::span<const NGLink>::iterator iterator_;
+  base::span<const NGLink>::iterator child_iterator_;
+  const NGBlockBreakToken* incoming_break_token_;
+  base::span<const NGBreakToken* const>::iterator break_token_iterator_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index f9fa0db..569cced 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -2468,12 +2468,6 @@
   return token_value;
 }
 
-void DocumentLoader::NotifyPrerenderingDocumentActivated() {
-  DCHECK(!frame_->GetDocument()->IsPrerendering());
-  DCHECK(is_prerendering_);
-  is_prerendering_ = false;
-}
-
 ContentSecurityPolicy* DocumentLoader::CreateCSP() {
   ContentSecurityPolicy* csp = MakeGarbageCollected<ContentSecurityPolicy>();
 
diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h
index 02a12491..93f1b6c 100644
--- a/third_party/blink/renderer/core/loader/document_loader.h
+++ b/third_party/blink/renderer/core/loader/document_loader.h
@@ -333,9 +333,8 @@
   // to ensure the token can only be used to invoke a single text fragment.
   bool ConsumeTextFragmentToken();
 
-  // Notifies that the prerendering document this loader is working for is
-  // activated.
-  void NotifyPrerenderingDocumentActivated();
+  // Returns whether the load request was initiated for prerendering.
+  bool IsPrerendering() const { return is_prerendering_; }
 
  protected:
   Vector<KURL> redirect_chain_;
@@ -553,8 +552,8 @@
   // Whether this load request was initiated by the browser.
   const bool is_browser_initiated_ = false;
 
-  // Whether this loader is working for a prerendering document.
-  bool is_prerendering_ = false;
+  // Whether this load request was initiated for prerendering.
+  const bool is_prerendering_ = false;
 
   // Whether this load request was initiated by the same origin.
   bool is_same_origin_navigation_ = false;
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index 417c216b..d555181 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -655,9 +655,11 @@
   }
 
   if (auto* app_history = AppHistory::appHistory(*frame_->DomWindow())) {
-    if (!app_history->DispatchNavigateEvent(url, request.Form(), false,
-                                            frame_load_type)) {
-      return;
+    if (request.GetNavigationPolicy() == kNavigationPolicyCurrentTab) {
+      if (!app_history->DispatchNavigateEvent(url, request.Form(), false,
+                                              frame_load_type)) {
+        return;
+      }
     }
   }
 
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
index d7472c6..99cfa18 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
@@ -7,32 +7,340 @@
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/page_scale_constraints_set.h"
 #include "third_party/blink/renderer/core/frame/visual_viewport.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
+#include "third_party/blink/renderer/core/html/html_anchor_element.h"
 #include "third_party/blink/renderer/core/layout/layout_image.h"
 #include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
-#include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/viewport_description.h"
+#include "third_party/blink/renderer/platform/heap/visitor.h"
 
 namespace blink {
 
 using mojom::blink::ViewportStatus;
+static constexpr int kSmallFontThreshold = 12;
+const base::Feature kBadTapTargetsRatio{"BadTapTargetsRatio",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
+static constexpr int kTimeBudgetExceeded = -2;
+
+// Finding bad tap targets may takes too time for big page and should abort if
+// it takes more than 5ms.
+static constexpr base::TimeDelta kTimeBudgetForBadTapTarget =
+    base::TimeDelta::FromMilliseconds(5);
 
 MobileFriendlinessChecker::MobileFriendlinessChecker(LocalFrameView& frame_view)
     : frame_view_(&frame_view),
       font_size_check_enabled_(frame_view_->GetFrame().GetWidgetForLocalRoot()),
-      needs_report_mf_(false),
+      tap_target_check_enabled_(
+          base::FeatureList::IsEnabled(kBadTapTargetsRatio) &&
+          frame_view_->GetFrame().GetWidgetForLocalRoot()),
       viewport_scalar_(
           font_size_check_enabled_
               ? frame_view_->GetPage()
                     ->GetChromeClient()
                     .WindowToViewportScalar(&frame_view_->GetFrame(), 1)
-              : 0) {}
+              : 0),
+      fcp_detected_(false) {}
 
 MobileFriendlinessChecker::~MobileFriendlinessChecker() = default;
 
+void MobileFriendlinessChecker::NotifyFirstContentfulPaint() {
+  fcp_detected_ = true;
+}
+
+namespace {
+
+bool IsTimeBudgetExpired(const base::Time& from) {
+  return base::Time::Now() - from > kTimeBudgetForBadTapTarget;
+}
+
+// Fenwick tree is a data structure which can efficiently update elements and
+// calculate prefix sums in an array of numbers. We use it here to track tap
+// targets which are too close.
+class FenwickTree {
+ public:
+  explicit FenwickTree(size_t n) : tree(n + 1) {}
+
+  // Returns prefix sum of the array from 0 to |index|.
+  int sum(size_t index) const {
+    int sum = 0;
+    for (index += 1; 0 < index; index -= index & -index)
+      sum += tree[index];
+    return sum;
+  }
+
+  // Adds |val| at |index| of the array.
+  void add(size_t index, int val) {
+    for (index += 1; index <= tree.size() - 1; index += index & -index)
+      tree[index] += val;
+  }
+
+ private:
+  Vector<int> tree;
+};
+
+// Stands for a vertex in the view, this is four corner or center of tap targets
+// rectangles.
+// Start edge means top edge of rectangle, End edge means bottom edge of
+// rectangle, Center means arithmetic mean of four corners.
+// In bad tap targets context, "Bad target" means a targets hard to tap
+// precisely because there are other targets which are too close to the target.
+struct EdgeOrCenter {
+  enum Type : int { kStartEdge = 0, kCenter = 1, kEndEdge = 2 } type;
+
+  union EdgeOrCenterUnion {
+    // Valid iff |type| is Edge.
+    struct Edge {
+      int left;
+      int right;
+    } edge;
+
+    // Valid iff |type| is Center.
+    int center;
+  } v;
+
+  static EdgeOrCenter StartEdge(int left, int right) {
+    EdgeOrCenter edge;
+    edge.type = EdgeOrCenter::kStartEdge;
+    edge.v.edge.left = left;
+    edge.v.edge.right = right;
+    return edge;
+  }
+
+  static EdgeOrCenter EndEdge(int left, int right) {
+    EdgeOrCenter edge;
+    edge.type = EdgeOrCenter::kEndEdge;
+    edge.v.edge.left = left;
+    edge.v.edge.right = right;
+    return edge;
+  }
+
+  static EdgeOrCenter Center(int center) {
+    EdgeOrCenter edge;
+    edge.type = EdgeOrCenter::kCenter;
+    edge.v.center = center;
+    return edge;
+  }
+};
+
+bool IsTapTargetCandidate(const Node* node) {
+  return IsA<HTMLFormControlElement>(node) ||
+         (IsA<HTMLAnchorElement>(node) &&
+          !To<HTMLAnchorElement>(node)->Href().IsEmpty());
+}
+
+// Scans full DOM tree and register all tap regions.
+// root: DOM tree's root.
+// finger_radius: Extends every tap regions with given pixels.
+// x_positions: Collects and inserts every x dimension positions.
+// vertices: Inserts y dimension keyed vertex positions with its attribute.
+// Returns total count of tap targets.
+// Returns -1 if time limit exceeded.
+int ExtractAndCountAllTapTargets(
+    LayoutObject* const root,
+    int finger_radius,
+    Vector<int>& x_positions,
+    const base::Time& started,
+    Vector<std::pair<int, EdgeOrCenter>>& vertices) {
+  vertices.clear();
+  int tap_targets = 0;
+  for (LayoutObject* object = root; object;) {
+    Node* node = object->GetNode();
+    const ComputedStyle* style = object->Style();
+    if (!node || !IsTapTargetCandidate(node)) {
+      object = object->NextInPreOrder();
+      continue;
+    }
+    if (object->IsElementContinuation() ||
+        style->Visibility() != EVisibility::kVisible ||
+        style->ContentVisibility() != EContentVisibility::kVisible) {
+      // Skip the whole subtree in this case. Some elements in subtree may have
+      // visibility: visible property which should not be ignored for
+      // correctness, but it is rare and we priority performance.
+      object = object->NextInPreOrderAfterChildren();
+      continue;
+    }
+    if (Element* element = DynamicTo<Element>(object->GetNode())) {
+      // Expand each corner by the size of fingertips.
+      const FloatRect rect = element->GetBoundingClientRectNoLifecycleUpdate();
+      if (rect.IsEmpty()) {
+        object = object->NextInPreOrder();
+        continue;
+      }
+      const int top = rect.Y() - finger_radius;
+      const int bottom = rect.MaxY() + finger_radius;
+      const int left = rect.X() - finger_radius;
+      const int right = rect.MaxX() + finger_radius;
+      const int center = (left + right) / 2;
+      vertices.emplace_back(top, EdgeOrCenter::StartEdge(left, right));
+      vertices.emplace_back((top + bottom) / 2, EdgeOrCenter::Center(center));
+      vertices.emplace_back(bottom, EdgeOrCenter::EndEdge(left, right));
+      x_positions.push_back(left);
+      x_positions.push_back(right);
+      x_positions.push_back(center);
+      tap_targets++;
+
+      if (IsTimeBudgetExpired(started))
+        return -1;
+    }
+    object = object->NextInPreOrder();
+  }
+  return tap_targets;
+}
+
+// Compress the x-dimension range and overwrites the value.
+// Precondition: |positions| must be sorted and unique.
+void CompressKeyWithVector(const Vector<int>& positions,
+                           Vector<std::pair<int, EdgeOrCenter>>& vertices) {
+  // Overwrite the vertex key with the position of the map.
+  for (auto& it : vertices) {
+    EdgeOrCenter& vertex = it.second;
+    switch (vertex.type) {
+      case EdgeOrCenter::kStartEdge:
+      case EdgeOrCenter::kEndEdge: {
+        vertex.v.edge.left =
+            std::distance(positions.begin(),
+                          std::lower_bound(positions.begin(), positions.end(),
+                                           vertex.v.edge.left));
+        vertex.v.edge.right =
+            std::distance(positions.begin(),
+                          std::lower_bound(positions.begin(), positions.end(),
+                                           vertex.v.edge.right));
+        break;
+      }
+      case EdgeOrCenter::kCenter: {
+        vertex.v.center =
+            std::distance(positions.begin(),
+                          std::lower_bound(positions.begin(), positions.end(),
+                                           vertex.v.center));
+        break;
+      }
+    }
+  }
+}
+
+// Scans the vertices from top to bottom with updating FenwickTree to track
+// tap target regions.
+// Precondition: |vertex| must be sorted by its |first|.
+// rightmost_position: Rightmost x position in all vertices.
+// Returns bad tap targets count.
+// Returns -1 if time limit exceeded.
+int CountBadTapTargets(int rightmost_position,
+                       const Vector<std::pair<int, EdgeOrCenter>>& vertices,
+                       const base::Time& started) {
+  FenwickTree tree(rightmost_position);
+  int bad_tap_targets = 0;
+  for (const auto& it : vertices) {
+    const EdgeOrCenter& vertex = it.second;
+    switch (vertex.type) {
+      case EdgeOrCenter::kStartEdge: {
+        // Tap region begins.
+        tree.add(vertex.v.edge.left, 1);
+        tree.add(vertex.v.edge.right, -1);
+        break;
+      }
+      case EdgeOrCenter::kEndEdge: {
+        // Tap region ends.
+        tree.add(vertex.v.edge.left, -1);
+        tree.add(vertex.v.edge.right, 1);
+        break;
+      }
+      case EdgeOrCenter::kCenter: {
+        // Iff the center of a tap target is included other than itself, it is a
+        // Bad Target.
+        if (tree.sum(vertex.v.center) > 1)
+          bad_tap_targets++;
+        break;
+      }
+    }
+    if (IsTimeBudgetExpired(started))
+      return -1;
+  }
+  return bad_tap_targets;
+}
+
+}  // namespace
+
+// Counts and calculate ration of bad tap targets. The process is a surface scan
+// with region tracking by Fenwick tree. The detail of the algorithm is
+// go/bad-tap-target-ukm
+void MobileFriendlinessChecker::ComputeBadTapTargetsRatio() {
+  base::Time started = base::Time::Now();
+  constexpr float kOneDipInMm = 0.15875;
+  const float scale_factor = frame_view_->GetChromeClient()
+                                 ->GetScreenInfo(frame_view_->GetFrame())
+                                 .device_scale_factor;
+  const int finger_radius =
+      std::floor((3 / kOneDipInMm) / scale_factor);  // 3mm in logical pixel.
+  Vector<std::pair<int, EdgeOrCenter>> vertices;
+  Vector<int> x_positions;
+
+  // Scan full DOM tree and extract every corner and center position of tap
+  // targets.
+  int all_tap_targets = ExtractAndCountAllTapTargets(
+      frame_view_->GetFrame().GetDocument()->GetLayoutView(), finger_radius,
+      x_positions, started, vertices);
+  if (all_tap_targets == -1) {
+    mobile_friendliness_.bad_tap_targets_ratio = kTimeBudgetExceeded;
+    return;
+  }
+
+  // Compress x dimension of all vertices to save memory.
+  // This will reduce rightmost position of vertices without sacrificing
+  // accuracy so that required memory by Fenwick Tree will be reduced.
+  std::sort(x_positions.begin(), x_positions.end());
+  x_positions.erase(std::unique(x_positions.begin(), x_positions.end()),
+                    x_positions.end());
+  CompressKeyWithVector(x_positions, vertices);
+
+  // Reorder vertices by y dimension for sweeping full page from top to bottom.
+  std::sort(vertices.begin(), vertices.end(),
+            [](const std::pair<int, EdgeOrCenter>& a,
+               const std::pair<int, EdgeOrCenter>& b) {
+              // Ordering with kStart < kCenter < kEnd.
+              return std::tie(a.first, a.second.type) <
+                     std::tie(b.first, b.second.type);
+            });
+
+  // Sweep x-compressed y-ordered vertices to detect bad tap targets.
+  const int bad_tap_targets =
+      CountBadTapTargets(x_positions.size(), vertices, started);
+  if (bad_tap_targets == -1) {
+    mobile_friendliness_.bad_tap_targets_ratio = kTimeBudgetExceeded;
+    return;
+  }
+
+  if (all_tap_targets > 0) {
+    mobile_friendliness_.bad_tap_targets_ratio =
+        bad_tap_targets * 100 / all_tap_targets;
+  } else {
+    mobile_friendliness_.bad_tap_targets_ratio = 0;
+  }
+}
+
+void MobileFriendlinessChecker::NotifyDocumentUnload() {
+  // If detached, there's no need to calculate any metrics.
+  if (!frame_view_->GetChromeClient())
+    return;
+
+  if (tap_target_check_enabled_)
+    ComputeBadTapTargetsRatio();
+
+  if (font_size_check_enabled_)
+    mobile_friendliness_.small_text_ratio = text_area_sizes_.SmallTextRatio();
+
+  // As long as evaluated as MF, TextOutsideViewportPercentage UKM must not be
+  // -1 (means unknown). Even if there is no call of
+  // ComputeTextContentOutsideViewport(), as far as there are FCP notification
+  // and unload event, that value is not -1 anymore and to be 0.
+  mobile_friendliness_.text_content_outside_viewport_percentage = std::max(
+      0, mobile_friendliness_.text_content_outside_viewport_percentage);
+
+  if (fcp_detected_)
+    frame_view_->DidChangeMobileFriendliness(mobile_friendliness_);
+}
+
 void MobileFriendlinessChecker::NotifyViewportUpdated(
     const ViewportDescription& viewport) {
   switch (viewport.type) {
@@ -50,7 +358,7 @@
       }
       if (viewport.zoom_is_explicit) {
         mobile_friendliness_.viewport_initial_scale_x10 =
-            std::floor(viewport.zoom * 10 + 0.5);
+            std::round(viewport.zoom * 10);
       }
       if (viewport.user_zoom_is_explicit) {
         mobile_friendliness_.allow_user_zoom =
@@ -60,7 +368,6 @@
     default:
       return;
   }
-  frame_view_->DidChangeMobileFriendliness(mobile_friendliness_);
 }
 
 int MobileFriendlinessChecker::TextAreaWithFontSize::SmallTextRatio() const {
@@ -73,9 +380,12 @@
     const LayoutObject& object) {
   ComputeTextContentOutsideViewport(object);
 
-  if (!font_size_check_enabled_)
-    return;
+  if (font_size_check_enabled_)
+    ComputeSmallTextRatio(object);
+}
 
+void MobileFriendlinessChecker::ComputeSmallTextRatio(
+    const LayoutObject& object) {
   if (const auto* text = DynamicTo<LayoutText>(object)) {
     const ComputedStyle* style = text->Style();
 
@@ -92,31 +402,15 @@
     actual_font_size /= viewport_scalar_;
 
     double area = text->PhysicalAreaSize();
-    if (actual_font_size < MobileFriendlinessChecker::kSmallFontThreshold)
+    if (actual_font_size < kSmallFontThreshold)
       text_area_sizes_.small_font_area += area;
+
     text_area_sizes_.total_text_area += area;
-
-    const int previous_mfs = mobile_friendliness_.small_text_ratio * 100;
-    mobile_friendliness_.small_text_ratio = text_area_sizes_.SmallTextRatio();
-    const int current_mfs = mobile_friendliness_.small_text_ratio * 100;
-    needs_report_mf_ = needs_report_mf_ || (previous_mfs != current_mfs);
   }
 }
 
-void MobileFriendlinessChecker::NotifyPrePaintFinished() {
-  if (!needs_report_mf_)
-    return;
-  DCHECK_EQ(frame_view_->GetFrame().GetDocument()->Lifecycle().GetState(),
-            DocumentLifecycle::kInPrePaint);
-  frame_view_->DidChangeMobileFriendliness(mobile_friendliness_);
-  needs_report_mf_ = false;
-}
-
 void MobileFriendlinessChecker::ComputeTextContentOutsideViewport(
     const LayoutObject& object) {
-  if (!frame_view_->GetFrame().IsMainFrame())
-    return;
-
   int frame_width = frame_view_->GetPage()->GetVisualViewport().Size().Width();
   if (frame_width == 0) {
     return;
@@ -124,6 +418,8 @@
 
   int total_text_width;
   int text_content_outside_viewport_percentage = 0;
+  if (frame_width == 0)
+    return;
 
   if (const auto* text = DynamicTo<LayoutText>(object)) {
     const ComputedStyle* style = text->Style();
@@ -149,15 +445,12 @@
     total_text_width *= initial_scale;
 
   if (total_text_width > frame_width) {
+    // We use ceil function here because we want to treat 100.1% as 101 which
+    // requires a scroll bar.
     text_content_outside_viewport_percentage =
-        ceil((total_text_width - frame_width) * 100.0 / frame_width);
+        std::ceil((total_text_width - frame_width) * 100.0 / frame_width);
   }
 
-  needs_report_mf_ =
-      needs_report_mf_ ||
-      (mobile_friendliness_.text_content_outside_viewport_percentage <
-       text_content_outside_viewport_percentage);
-
   mobile_friendliness_.text_content_outside_viewport_percentage =
       std::max(mobile_friendliness_.text_content_outside_viewport_percentage,
                text_content_outside_viewport_percentage);
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h
index f115ac9..64749eeb 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h
@@ -16,25 +16,26 @@
 class LayoutObject;
 struct ViewportDescription;
 
+CORE_EXPORT extern const base::Feature kBadTapTargetsRatio;
+
 // Calculates the mobile usability of current page, especially friendliness on
 // smart phone devices are checked. The calculated value will be sent as a part
 // of UKM.
 class CORE_EXPORT MobileFriendlinessChecker
     : public GarbageCollected<MobileFriendlinessChecker> {
-  static constexpr int kSmallFontThreshold = 12;
-
  public:
   explicit MobileFriendlinessChecker(LocalFrameView& frame_view);
   virtual ~MobileFriendlinessChecker();
 
+  void NotifyFirstContentfulPaint();
   void NotifyViewportUpdated(const ViewportDescription&);
   void NotifyInvalidatePaint(const LayoutObject& object);
-  void NotifyPrePaintFinished();
   const blink::MobileFriendliness& GetMobileFriendliness() const {
     return mobile_friendliness_;
   }
-  void Trace(Visitor* visitor) const;
+  void NotifyDocumentUnload();
 
+  void Trace(Visitor* visitor) const;
   struct TextAreaWithFontSize {
     double small_font_area = 0;
     double total_text_area = 0;
@@ -42,14 +43,18 @@
   };
 
  private:
+  void ComputeSmallTextRatio(const LayoutObject& object);
   void ComputeTextContentOutsideViewport(const LayoutObject& object);
+  void ComputeBadTapTargetsRatio();
 
+ private:
   TextAreaWithFontSize text_area_sizes_;
   Member<LocalFrameView> frame_view_;
   blink::MobileFriendliness mobile_friendliness_;
   bool font_size_check_enabled_;
-  bool needs_report_mf_;
+  bool tap_target_check_enabled_;
   float viewport_scalar_;
+  bool fcp_detected_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc
index 6611476..ebe6dbe 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h"
 #include "base/test/scoped_feature_list.h"
 #include "third_party/blink/public/common/mobile_metrics/mobile_friendliness.h"
+#include "third_party/blink/public/mojom/mobile_metrics/mobile_friendliness.mojom-shared.h"
 #include "third_party/blink/public/web/web_settings.h"
 #include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
@@ -14,6 +15,8 @@
 
 namespace blink {
 
+using mojom::ViewportStatus;
+
 static constexpr char kBaseUrl[] = "http://www.test.com/";
 class MobileFriendlinessCheckerTest : public testing::Test {
  public:
@@ -21,6 +24,10 @@
     url_test_helpers::UnregisterAllURLsAndClearMemoryCache();
   }
 
+  void SetUp() override {
+    scoped_feature_list_.InitWithFeatures({kBadTapTargetsRatio}, {});
+  }
+
   static void ConfigureAndroidSettings(WebSettings* settings) {
     settings->SetViewportEnabled(true);
     settings->SetViewportMetaEnabled(true);
@@ -43,6 +50,7 @@
       LocalFrameView& frame_view =
           *helper.GetWebView()->MainFrameImpl()->GetFrameView();
       frame_view.UpdateLifecycleToPrePaintClean(DocumentUpdateReason::kTest);
+      frame_view.GetMobileFriendlinessChecker()->NotifyFirstContentfulPaint();
     }
     return web_frame_client.GetMobileFriendliness();
   }
@@ -66,6 +74,7 @@
       LocalFrameView& frame_view =
           *helper.GetWebView()->MainFrameImpl()->GetFrameView();
       frame_view.UpdateLifecycleToPrePaintClean(DocumentUpdateReason::kTest);
+      frame_view.GetMobileFriendlinessChecker()->NotifyFirstContentfulPaint();
     }
     return web_frame_client.GetMobileFriendliness();
   }
@@ -121,7 +130,8 @@
       CalculateMetricsForHTMLString(R"(<body></body>)");
   EXPECT_EQ(actual_mf.viewport_device_width, mojom::ViewportStatus::kNo);
   EXPECT_EQ(actual_mf.allow_user_zoom, mojom::ViewportStatus::kYes);
-  EXPECT_EQ(actual_mf.small_text_ratio, -1);
+  EXPECT_EQ(actual_mf.small_text_ratio, 0);
+  EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 0);
 }
 
 TEST_F(MobileFriendlinessCheckerTest, NoSmallFonts) {
@@ -329,7 +339,7 @@
   </body>
 </html>
 )");
-  EXPECT_EQ(actual_mf.text_content_outside_viewport_percentage, -1);
+  EXPECT_EQ(actual_mf.text_content_outside_viewport_percentage, 0);
 }
 
 TEST_F(MobileFriendlinessCheckerTest, ImageNarrow) {
@@ -362,7 +372,7 @@
   </body>
 </html>
 )");
-  EXPECT_EQ(actual_mf.text_content_outside_viewport_percentage, -1);
+  EXPECT_EQ(actual_mf.text_content_outside_viewport_percentage, 0);
 }
 
 TEST_F(MobileFriendlinessCheckerTest, ScaleTextOutsideViewport) {
@@ -389,4 +399,302 @@
   EXPECT_GE(actual_mf.text_content_outside_viewport_percentage, 100.0);
 }
 
+TEST_F(MobileFriendlinessCheckerTest, SingleTapTarget) {
+  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  <head>
+    <meta name="viewport" content="width=480, initial-scale=1">
+  </head>
+  <body style="font-size: 18px">
+    <a onclick="alert('clicked');">
+      link
+    </a>
+  </body>
+)");
+  EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 0);
+}
+
+TEST_F(MobileFriendlinessCheckerTest, NoBadTapTarget) {
+  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  <head>
+    <meta name="viewport" content="width=480, initial-scale=1">
+  </head>
+  <body style="font-size: 18px">
+    <button style="width:30px; height:30px">
+      a
+    </button>
+    <button style="width:30px; height:30px">
+      b
+    </button>
+  </body>
+)");
+  EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 0);
+}
+
+TEST_F(MobileFriendlinessCheckerTest, TooCloseTapTargetsVertical) {
+  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  <head>
+    <meta name="viewport" content="width=480, initial-scale=1">
+  </head>
+  <body style="font-size: 18px">
+    <a href="about:blank">
+      <div style="width: 400px;height: 400px; margin: 0px">
+        A
+      </div>
+    </a>
+    <a href="about:blank">
+      <div style="width: 10px;height: 10px; margin: 0px">
+        B
+      </div>
+    </a>
+  </body>
+)");
+  EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 50);
+}
+
+TEST_F(MobileFriendlinessCheckerTest, TooCloseTapTargetsVerticalSamePoint) {
+  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  <head>
+    <meta name="viewport" content="width=480, initial-scale=1">
+  </head>
+  <body style="font-size: 18px">
+    <a href="about:blank">
+      <div style="width: 400px;height: 400px; margin: 0px">
+        A
+      </div>
+    </a>
+    <a href="about:blank">
+      <div style="width: 10px;height: 10px; margin: 0px">
+        B
+      </div>
+    </a>
+    <a href="about:blank">
+      <div style="width: 400px;height: 400px; margin: 0px">
+        C
+      </div>
+    </a>
+  </body>
+)");
+  EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 33);
+}
+
+TEST_F(MobileFriendlinessCheckerTest, TooCloseTapTargetsHorizontal) {
+  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  <head>
+    <meta name="viewport" content="width=480, initial-scale=1">
+  </head>
+  <body style="font-size: 18px">
+    <a href="about:blank">
+      <div style="width: 300px;height: 300px; margin: 0px; display:inline-block">
+        A
+      </div>
+    </a>
+    <a href="about:blank">
+      <div style="width: 10px;height: 10px; margin: 0px; display:inline-block">
+        B
+      </div>
+    </a>
+  </body>
+)");
+  EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 50);
+}
+
+TEST_F(MobileFriendlinessCheckerTest, TooCloseTapTargetsHorizontalSamePoint) {
+  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  <head>
+    <meta name="viewport" content="width=480, initial-scale=1">
+  </head>
+  <body style="font-size: 18px">
+    <a href="about:blank">
+      <div style="width: 200px;height: 200px; margin: 0px; display:inline-block">
+        A
+      </div>
+    </a>
+    <a href="about:blank">
+      <div style="width: 10px;height: 10px; margin: 0px; display:inline-block">
+        B
+      </div>
+    </a>
+    <a href="about:blank">
+      <div style="width: 200px;height: 200px; margin: 0px; display:inline-block">
+        C
+      </div>
+    </a>
+  </body>
+)");
+  EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 33);
+}
+
+TEST_F(MobileFriendlinessCheckerTest, GridGoodTargets3X3) {
+  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  <head>
+    <meta name="viewport" content="width=480, initial-scale=1">
+  </head>
+  <body style="font-size: 18px">
+    <div>
+      <a href="about:blank">
+        <div style="width: 50px;height: 50px; margin: 50px; display:inline-block">
+          1-1
+        </div>
+      </a>
+      <a href="about:blank">
+        <div style="width: 50px;height: 50px; margin: 50px; display:inline-block">
+          2-1
+        </div>
+      </a>
+      <a href="about:blank">
+        <div style="width: 50px;height: 50px; margin: 50px; display:inline-block">
+          3-1
+        </div>
+      </a>
+    </div>
+    <div>
+      <a href="about:blank">
+        <div style="width: 50px;height: 50px; margin: 50px; display:inline-block">
+          1-2
+        </div>
+      </a>
+      <a href="about:blank">
+        <div style="width: 50px;height: 50px; margin: 50px; display:inline-block">
+          2-2
+        </div>
+      </a>
+      <a href="about:blank">
+        <div style="width: 50px;height: 50px; margin: 50px; display:inline-block">
+          3-2
+        </div>
+      </a>
+    </div>
+    <div>
+      <a href="about:blank">
+        <div style="width: 50px;height: 50px; margin: 50px; display:inline-block">
+          1-3
+        </div>
+      </a>
+      <a href="about:blank">
+        <div style="width: 50px;height: 50px; margin: 50px; display:inline-block">
+          2-3
+        </div>
+      </a>
+      <a href="about:blank">
+        <div style="width: 50px;height: 50px; margin: 50px; display:inline-block">
+          3-3
+        </div>
+      </a>
+    </div>
+  </body>
+)");
+  EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 0);
+}
+
+TEST_F(MobileFriendlinessCheckerTest, GridBadTargets3X3) {
+  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  <head>
+    <meta name="viewport" content="width=480, initial-scale=1">
+  </head>
+  <body style="font-size: 18px">
+    <div>
+      <a href="about:blank">
+        <div style="width: 10px;height: 10px; margin: 10px; display:inline-block">
+          1-1
+        </div>
+      </a>
+      <a href="about:blank">
+        <div style="width: 10px;height: 10px; margin: 10px; display:inline-block">
+          2-1
+        </div>
+      </a>
+      <a href="about:blank">
+        <div style="width: 10px;height: 10px; margin: 10px; display:inline-block">
+          3-1
+        </div>
+      </a>
+    </div>
+    <div>
+      <a href="about:blank">
+        <div style="width: 10px;height: 10px; margin: 10px; display:inline-block">
+          1-2
+        </div>
+      </a>
+      <a href="about:blank">
+        <div style="width: 10px;height: 10px; margin: 10px; display:inline-block">
+          2-2
+        </div>
+      </a>
+      <a href="about:blank">
+        <div style="width: 10px;height: 10px; margin: 10px; display:inline-block">
+          3-2
+        </div>
+      </a>
+    </div>
+    <div>
+      <a href="about:blank">
+        <div style="width: 10px;height: 10px; margin: 10px; display:inline-block">
+          1-3
+        </div>
+      </a>
+      <a href="about:blank">
+        <div style="width: 10px;height: 10px; margin: 10px; display:inline-block">
+          2-3
+        </div>
+      </a>
+      <a href="about:blank">
+        <div style="width: 10px;height: 10px; margin: 10px; display:inline-block">
+          3-3
+        </div>
+      </a>
+    </div>
+  </body>
+)");
+  EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 100);
+}
+
+TEST_F(MobileFriendlinessCheckerTest, FormTapTargets) {
+  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  <head>
+    <meta name="viewport" content="width=480, initial-scale=1">
+  </head>
+  <body style="font-size: 18px">
+    <form>
+      <input style="height: 400px; margin: 0px"><br>
+      <input style="height: 10px; margin: 0px">
+    </form>
+  </body>
+)");
+  EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 50);
+}
+
+TEST_F(MobileFriendlinessCheckerTest, InvisibleTapTargetWillBeIgnored) {
+  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  <head>
+    <meta name="viewport" content="width=480, initial-scale=1">
+  </head>
+  <body style="font-size: 18px">
+    <form>
+      <input style="height: 400px; margin: 0px"><br>
+      <div style="display:none">
+        <input style="height: 10px; margin: 0px">
+      </div>
+    </form>
+  </body>
+)");
+  EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 0);
+}
+
+TEST_F(MobileFriendlinessCheckerTest, BadTapTargetWithPositionAbsolute) {
+  MobileFriendliness actual_mf = CalculateMetricsForHTMLString(R"(
+  <head>
+    <meta name="viewport" content="width=480, initial-scale=1">
+  </head>
+  <body style="font-size: 18px">
+    <button style="position:absolute; width:50px; height:50px">
+      a
+    </button>
+    <button style="position:relative; width:50px; height:50px">
+      b
+    </button>
+  </body>
+)");
+  EXPECT_EQ(actual_mf.bad_tap_targets_ratio, 100);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
index efd5492..af029d5 100644
--- a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
+++ b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
@@ -502,10 +502,10 @@
 
 void OffscreenCanvas::SetFilterQualityInResource(
     SkFilterQuality filter_quality) {
-  if (filter_quality_ == filter_quality)
+  if (FilterQuality() == filter_quality)
     return;
 
-  filter_quality_ = filter_quality;
+  SetFilterQuality(filter_quality);
   if (ResourceProvider())
     GetOrCreateResourceProvider()->SetFilterQuality(filter_quality);
 }
diff --git a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h
index 3998873..b85afc14 100644
--- a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h
+++ b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h
@@ -100,10 +100,6 @@
   uint32_t ClientId() const { return client_id_; }
   uint32_t SinkId() const { return sink_id_; }
 
-  void SetFilterQuality(const SkFilterQuality& quality) {
-    filter_quality_ = quality;
-  }
-
   void AllowHighPerformancePowerPreference() {
     allow_high_performance_power_preference_ = true;
   }
@@ -130,7 +126,6 @@
   void SetNeedsCompositingUpdate() override {}
   // TODO(fserb): Merge this with HTMLCanvasElement::UpdateMemoryUsage
   void UpdateMemoryUsage() override;
-  SkFilterQuality FilterQuality() const override { return filter_quality_; }
 
   // EventTarget implementation
   const AtomicString& InterfaceName() const final {
@@ -253,8 +248,6 @@
   bool needs_push_frame_ = false;
   bool inside_worker_raf_ = false;
 
-  SkFilterQuality filter_quality_ = kLow_SkFilterQuality;
-
   // An offscreen canvas should only prefer the high-performance GPU if it is
   // initialized by transferring control from an HTML canvas that is not
   // cross-origin.
diff --git a/third_party/blink/renderer/core/page/context_menu_controller_test.cc b/third_party/blink/renderer/core/page/context_menu_controller_test.cc
index 7d20321..86f30bfd 100644
--- a/third_party/blink/renderer/core/page/context_menu_controller_test.cc
+++ b/third_party/blink/renderer/core/page/context_menu_controller_test.cc
@@ -119,13 +119,10 @@
 
   bool ShowContextMenu(const PhysicalOffset& location,
                        WebMenuSourceType source) {
-    bool success =
-        web_view_helper_.GetWebView()
-            ->GetPage()
-            ->GetContextMenuController()
-            .ShowContextMenu(GetDocument()->GetFrame(), location, source);
-    base::RunLoop().RunUntilIdle();
-    return success;
+    return web_view_helper_.GetWebView()
+        ->GetPage()
+        ->GetContextMenuController()
+        .ShowContextMenu(GetDocument()->GetFrame(), location, source);
   }
 
   bool ShowContextMenuForElement(Element* element, WebMenuSourceType source) {
diff --git a/third_party/blink/renderer/core/page/page.cc b/third_party/blink/renderer/core/page/page.cc
index a4ddff05..415b293 100644
--- a/third_party/blink/renderer/core/page/page.cc
+++ b/third_party/blink/renderer/core/page/page.cc
@@ -1066,26 +1066,6 @@
   return autoplay_flags_;
 }
 
-// https://jeremyroman.github.io/alternate-loading-modes/#prerendering-browsing-context-activate
-void Page::ActivateForPrerendering() {
-  DCHECK(features::IsPrerender2Enabled());
-
-  // Step 8.2. "Let inclusiveDescendants be successorBC extended with
-  // successorBC's active document's list of the descendant browsing contexts."
-  // Step 8.3. "For each bc of inclusiveDescendants, queue a global task on the
-  // networking task source, given bc's active window, to perform the following
-  // steps:"
-  for (Frame* frame = MainFrame(); frame;
-       frame = frame->Tree().TraverseNext()) {
-    if (auto* local_frame = DynamicTo<LocalFrame>(frame)) {
-      local_frame->GetTaskRunner(TaskType::kNetworking)
-          ->PostTask(FROM_HERE,
-                     WTF::Bind(&Document::ActivateForPrerendering,
-                               WrapPersistent(local_frame->GetDocument())));
-    }
-  }
-}
-
 void Page::SetInsidePortal(bool inside_portal) {
   inside_portal_ = inside_portal;
 }
diff --git a/third_party/blink/renderer/core/page/page.h b/third_party/blink/renderer/core/page/page.h
index 61d780c0..d2efc89d 100644
--- a/third_party/blink/renderer/core/page/page.h
+++ b/third_party/blink/renderer/core/page/page.h
@@ -336,8 +336,6 @@
 
   int32_t AutoplayFlags() const;
 
-  void ActivateForPrerendering();
-
   void SetInsidePortal(bool inside_portal);
   bool InsidePortal() const;
 
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc
index 6366c74..4148d4e 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc
@@ -168,7 +168,7 @@
 }
 
 void TextFragmentFinder::FindPrefix() {
-  search_range_->setStart(ToPositionInDOMTree(match_range_start_));
+  search_range_->setStart(match_range_->StartPosition());
   if (search_range_->collapsed()) {
     OnMatchComplete();
     return;
@@ -199,7 +199,8 @@
   // with the current one. e.g. If |prefix| is "a a" and our search range
   // currently starts with "a a a b...", the next iteration should start at
   // the second a which is part of the current |prefix_match|.
-  match_range_start_ = FirstWordBoundaryAfter(prefix_match.StartPosition());
+  match_range_->setStart(ToPositionInDOMTree(
+      FirstWordBoundaryAfter(prefix_match.StartPosition())));
   SetPrefixMatch(prefix_match);
   GoToStep(kMatchTextStart);
   return;
@@ -220,7 +221,8 @@
   EphemeralRangeInFlatTree potential_match;
   if (prefix_match.IsNotNull()) {
     search_range_->setStart(ToPositionInDOMTree(
-        NextTextPosition(prefix_match.EndPosition(), match_range_end_)));
+        NextTextPosition(prefix_match.EndPosition(),
+                         ToPositionInFlatTree(match_range_->EndPosition()))));
     FindMatchInRange(selector_.Start(), search_range_,
                      /*word_start_bounded=*/false, end_at_word_boundary);
   } else {
@@ -234,8 +236,9 @@
   EphemeralRangeInFlatTree prefix_match(prefix_match_);
   if (prefix_match.IsNotNull()) {
     EphemeralRangeInFlatTree match_range(
-        NextTextPosition(prefix_match.EndPosition(), match_range_end_),
-        match_range_end_);
+        NextTextPosition(prefix_match.EndPosition(),
+                         ToPositionInFlatTree(match_range_->EndPosition())),
+        ToPositionInFlatTree(match_range_->EndPosition()));
     // We found a potential match but it didn't immediately follow the prefix.
     if (!potential_match.IsNull() &&
         potential_match.StartPosition() != match_range.StartPosition()) {
@@ -251,8 +254,8 @@
     return;
   }
   if (prefix_match.IsNull()) {
-    match_range_start_ =
-        FirstWordBoundaryAfter(potential_match.StartPosition());
+    match_range_->setStart(ToPositionInDOMTree(
+        FirstWordBoundaryAfter(potential_match.StartPosition())));
   }
   range_end_search_start_ = potential_match.EndPosition();
   SetPotentialMatch(potential_match);
@@ -299,7 +302,8 @@
 
   // Now we just have to ensure the match is followed by the |suffix|.
   search_range_->setStart(ToPositionInDOMTree(
-      NextTextPosition(potential_match.EndPosition(), match_range_end_)));
+      NextTextPosition(potential_match.EndPosition(),
+                       ToPositionInFlatTree(match_range_->EndPosition()))));
   FindMatchInRange(selector_.Suffix(), search_range_,
                    /*word_start_bounded=*/false, /*word_end_bounded=*/true);
 }
@@ -315,8 +319,9 @@
   }
   EphemeralRangeInFlatTree potential_match(potential_match_);
   EphemeralRangeInFlatTree suffix_range(
-      NextTextPosition(potential_match.EndPosition(), match_range_end_),
-      match_range_end_);
+      NextTextPosition(potential_match.EndPosition(),
+                       ToPositionInFlatTree(match_range_->EndPosition())),
+      ToPositionInFlatTree(match_range_->EndPosition()));
   if (suffix_match.StartPosition() == suffix_range.StartPosition()) {
     OnMatchComplete();
     return;
@@ -381,13 +386,9 @@
   }
 }
 
-void TextFragmentFinder::Cancel() {
+void TextFragmentFinder::FindMatch() {
   if (find_buffer_runner_ && find_buffer_runner_->IsActive())
     find_buffer_runner_->Cancel();
-}
-
-void TextFragmentFinder::FindMatch() {
-  Cancel();
 
   auto forced_lock_scope =
       document_->GetDisplayLockDocumentState().GetScopedForceActivatableLocks();
@@ -410,8 +411,9 @@
   search_range_ = Range::Create(*document_);
   search_range_->setStart(ToPositionInDOMTree(search_start));
   search_range_->setEnd(ToPositionInDOMTree(search_end));
-  match_range_start_ = search_start;
-  match_range_end_ = search_end;
+  match_range_ = Range::Create(*document_);
+  match_range_->setStart(ToPositionInDOMTree(search_start));
+  match_range_->setEnd(ToPositionInDOMTree(search_end));
   potential_match_.Clear();
   prefix_match_.Clear();
   GoToStep(kMatchPrefix);
@@ -459,8 +461,7 @@
   visitor->Trace(prefix_match_);
   visitor->Trace(first_match_);
   visitor->Trace(search_range_);
-  visitor->Trace(match_range_start_);
-  visitor->Trace(match_range_end_);
+  visitor->Trace(match_range_);
   visitor->Trace(find_buffer_runner_);
 }
 
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h
index 8619354c..46e5089 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h
@@ -52,8 +52,6 @@
   // Begins searching in the given top-level document.
   void FindMatch();
 
-  void Cancel();
-
   void Trace(Visitor*) const;
 
  private:
@@ -119,9 +117,7 @@
   Member<Range> search_range_;
   // Range used for search for |potential_match_|.
   // https://wicg.github.io/scroll-to-text-fragment/#ref-for-range-collapsed:~:text=Let-,matchRange
-  // Member<Range> match_range_;
-  PositionInFlatTree match_range_start_;
-  PositionInFlatTree match_range_end_;
+  Member<Range> match_range_;
   // Used for running FindBuffer tasks.
   Member<FindBufferRunner> find_buffer_runner_;
 };
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc
index 6fe8778..53cbef0 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc
@@ -6,7 +6,6 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "base/time/default_tick_clock.h"
-#include "components/shared_highlighting/core/common/features.h"
 #include "components/shared_highlighting/core/common/shared_highlighting_metrics.h"
 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
 #include "third_party/blink/public/platform/interface_registry.h"
@@ -109,7 +108,7 @@
 Node* FurthestVisibleTextNodeWithinBlock(Node* start_node) {
   // Move forward/backward until no next/previous node is available within same
   // |block_ancestor|.
-  Node* last_node = nullptr;
+  Node* last_node;
   for (Node* node = start_node; node; node = Direction::Next(*node)) {
     node = Direction::GetVisibleTextNode(*node);
     if (node && !node->GetLayoutObject())
@@ -194,8 +193,6 @@
       selection_range.GetDocument(),
       ToPositionInDOMTree(selection_range.StartPosition()),
       ToPositionInDOMTree(selection_range.EndPosition()));
-  if (base::FeatureList::IsEnabled(features::kPreemptiveLinkToTextGeneration))
-    GenerateSelector();
 }
 
 void TextFragmentSelectorGenerator::BindTextFragmentSelectorProducer(
@@ -217,6 +214,7 @@
   Node* start_container =
       ephemeral_range.StartPosition().ComputeContainerNode();
   Node* end_container = ephemeral_range.EndPosition().ComputeContainerNode();
+
   Node* corrected_start =
       ResolvePositionToNode(ephemeral_range.StartPosition());
   int corrected_start_offset =
@@ -290,52 +288,30 @@
   }
 }
 
-void TextFragmentSelectorGenerator::Cancel() {
-  Reset();
-}
-
-void TextFragmentSelectorGenerator::RequestSelector(
-    RequestSelectorCallback callback) {
-  DCHECK(callback);
-  pending_generate_selector_callback_ = std::move(callback);
-  if (!base::FeatureList::IsEnabled(
-          features::kPreemptiveLinkToTextGeneration)) {
-    GenerateSelector();
-  } else {
-    DCHECK_NE(state_, kNotStarted);
-    if (state_ == kFailure) {
-      std::move(pending_generate_selector_callback_)
-          .Run(
-              TextFragmentSelector(TextFragmentSelector::SelectorType::kInvalid)
-                  .ToString());
-    } else if (state_ == kSuccess) {
-      std::move(pending_generate_selector_callback_).Run(selector_->ToString());
-    }
-  }
-}
-
-void TextFragmentSelectorGenerator::GenerateSelector() {
+void TextFragmentSelectorGenerator::GenerateSelector(
+    GenerateSelectorCallback callback) {
   DCHECK(selection_range_);
+  DCHECK(callback);
 
-  Reset();
-  selection_range_->OwnerDocument().UpdateStyleAndLayout(
-      DocumentUpdateReason::kFindInPage);
-
-  // Shouldn't continue is selection is empty.
-  EphemeralRangeInFlatTree ephemeral_range(selection_range_);
-  String selected_text = PlainText(ephemeral_range).StripWhiteSpace();
-  if (selected_text.IsEmpty()) {
-    state_ = kFailure;
-    error_ = LinkGenerationError::kEmptySelection;
-    ResolveSelectorState();
-    return;
-  }
+  generation_start_time_ = base::DefaultTickClock::GetInstance()->NowTicks();
+  pending_generate_selector_callback_ = std::move(callback);
+  state_ = kNeedsNewCandidate;
+  error_.reset();
+  step_ = kExact;
+  max_available_prefix_ = "";
+  max_available_suffix_ = "";
+  max_available_range_start_ = "";
+  max_available_range_end_ = "";
+  num_context_words_ = 0;
+  num_range_words_ = 0;
+  iteration_ = 0;
+  selector_ = nullptr;
 
   AdjustSelection();
   UMA_HISTOGRAM_COUNTS_1000(
       "SharedHighlights.LinkGenerated.SelectionLength",
       PlainText(EphemeralRange(selection_range_)).length());
-  state_ = kNeedsNewCandidate;
+
   GenerateSelectorCandidate();
 }
 
@@ -358,7 +334,6 @@
     case kTestCandidate:
       RunTextFinder();
       break;
-    case kNotStarted:
     case kNeedsNewCandidate:
       NOTREACHED();
       ABSL_FALLTHROUGH_INTENDED;
@@ -387,7 +362,7 @@
     const TextFragmentAnchorMetrics::Match match_metrics,
     bool is_unique) {
   if (is_unique && PlainText(match).StripWhiteSpace().length() ==
-                       PlainText(EphemeralRangeInFlatTree(selection_range_))
+                       PlainText(EphemeralRange(selection_range_))
                            .StripWhiteSpace()
                            .length()) {
     state_ = kSuccess;
@@ -410,6 +385,7 @@
 
 void TextFragmentSelectorGenerator::NotifySelectorReady(
     const TextFragmentSelector& selector) {
+  DCHECK(pending_generate_selector_callback_);
   // TODO(crbug.com/1133823): Add unit tests for all SharedHighlights.*
   // histograms.
   UMA_HISTOGRAM_BOOLEAN(
@@ -448,8 +424,7 @@
                                                        error);
   }
 
-  if (pending_generate_selector_callback_)
-    std::move(pending_generate_selector_callback_).Run(selector.ToString());
+  std::move(pending_generate_selector_callback_).Run(selector.ToString());
 }
 
 void TextFragmentSelectorGenerator::ClearSelection() {
@@ -478,7 +453,14 @@
     step_ = kRange;
     return;
   }
+
   String selected_text = PlainText(ephemeral_range).StripWhiteSpace();
+  if (selected_text.IsEmpty()) {
+    state_ = kFailure;
+    error_ = LinkGenerationError::kEmptySelection;
+    return;
+  }
+
   // If too long should use ranges.
   if (selected_text.length() > kExactTextMaxChars) {
     step_ = kRange;
@@ -660,22 +642,4 @@
   auto range_end = Position(suffix_end, suffix_end->textContent().length());
   return PlainText(EphemeralRange(range_start, range_end)).StripWhiteSpace();
 }
-
-void TextFragmentSelectorGenerator::Reset() {
-  if (finder_)
-    finder_->Cancel();
-
-  generation_start_time_ = base::DefaultTickClock::GetInstance()->NowTicks();
-  state_ = kNotStarted;
-  error_.reset();
-  step_ = kExact;
-  max_available_prefix_ = "";
-  max_available_suffix_ = "";
-  max_available_range_start_ = "";
-  max_available_range_end_ = "";
-  num_context_words_ = 0;
-  num_range_words_ = 0;
-  iteration_ = 0;
-  selector_ = nullptr;
-}
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h
index 5979c10d..a2ff2f0 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h
@@ -49,10 +49,8 @@
   void AdjustSelection();
 
   // blink::mojom::blink::TextFragmentSelectorProducer interface
-  void Cancel() override;
-
-  // Requests selector for current selection.
-  void RequestSelector(RequestSelectorCallback callback) override;
+  // Generates selector for current selection.
+  void GenerateSelector(GenerateSelectorCallback callback) override;
 
   // TextFragmentFinder::Client interface
   void DidFindMatch(const EphemeralRangeInFlatTree& match,
@@ -83,9 +81,6 @@
 
   // Used for determining the current state of |selector_|.
   enum SelectorState {
-    // Sreach for candidate selector didn't start.
-    kNotStarted,
-
     // Candidate selector should be generated or extended.
     kNeedsNewCandidate,
 
@@ -101,9 +96,6 @@
     kSuccess
   };
 
-  // Generates selector for current selection.
-  void GenerateSelector();
-
   void GenerateSelectorCandidate();
 
   void ResolveSelectorState();
@@ -121,8 +113,6 @@
   void ExtendRangeSelector();
   void ExtendContext();
 
-  void Reset();
-
   Member<LocalFrame> selection_frame_;
   Member<Range> selection_range_;
   std::unique_ptr<TextFragmentSelector> selector_;
@@ -132,7 +122,7 @@
   HeapMojoReceiver<blink::mojom::blink::TextFragmentSelectorProducer,
                    TextFragmentSelectorGenerator>
       selector_producer_{this, nullptr};
-  RequestSelectorCallback pending_generate_selector_callback_;
+  GenerateSelectorCallback pending_generate_selector_callback_;
 
   GenerationStep step_ = kExact;
   SelectorState state_ = kNeedsNewCandidate;
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc
index 6dc8546..445ad20 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc
@@ -96,7 +96,7 @@
     GetDocument()
         .GetFrame()
         ->GetTextFragmentSelectorGenerator()
-        ->RequestSelector(std::move(callback));
+        ->GenerateSelector(std::move(callback));
     base::RunLoop().RunUntilIdle();
 
     EXPECT_TRUE(callback_called);
@@ -1127,44 +1127,6 @@
   VerifySelector(start, end, "First%20paragraph,Second");
 }
 
-// Checks selection across an input element.
-TEST_F(TextFragmentSelectorGeneratorTest, Input_Submit) {
-  SimRequest request("https://example.com/test.html", "text/html");
-  LoadURL("https://example.com/test.html");
-  request.Complete(R"HTML(
-    <!DOCTYPE html>
-  <div id='div'>
-    First paragraph<input type='submit' value="button text"> Second paragraph
-  </div>
-  )HTML");
-  GetDocument().UpdateStyleAndLayoutTree();
-  Node* div = GetDocument().getElementById("div");
-  const auto& start = Position(div->firstChild(), 0);
-  const auto& end = Position(div->lastChild(), 7);
-  ASSERT_EQ("First paragraph Second", PlainText(EphemeralRange(start, end)));
-
-  VerifySelector(start, end, "First%20paragraph,Second");
-}
-
-// Checks selection across an input element.
-TEST_F(TextFragmentSelectorGeneratorTest, Input_Submit_prefix) {
-  SimRequest request("https://example.com/test.html", "text/html");
-  LoadURL("https://example.com/test.html");
-  request.Complete(R"HTML(
-    <!DOCTYPE html>
-  <div id='div'>
-    <input type='submit' value="button text"> paragraph text
-  </div>
-  )HTML");
-  GetDocument().UpdateStyleAndLayoutTree();
-  Node* div = GetDocument().getElementById("div");
-  const auto& start = Position(div->lastChild(), 0);
-  const auto& end = Position(div->lastChild(), 10);
-  ASSERT_EQ(" paragraph", PlainText(EphemeralRange(start, end)));
-
-  VerifySelector(start, end, "button%20text-,paragraph,-text");
-}
-
 // Basic test case for |GetNextTextBlock|.
 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock) {
   SimRequest request("https://example.com/test.html", "text/html");
diff --git a/third_party/blink/renderer/core/paint/paint_invalidator.cc b/third_party/blink/renderer/core/paint/paint_invalidator.cc
index 77bef95..da7d2373 100644
--- a/third_party/blink/renderer/core/paint/paint_invalidator.cc
+++ b/third_party/blink/renderer/core/paint/paint_invalidator.cc
@@ -200,14 +200,17 @@
       *tree_builder_context.current.clip, *tree_builder_context.current_effect);
 
   // Adjust old_paint_offset so that LayoutShiftTracker will see the change of
-  // offset caused by change of paint offset translations below the layout shift
-  // root.
-  PhysicalOffset adjusted_old_transform_indifferent_paint_offset =
-      context.old_paint_offset -
-      tree_builder_context.current.additional_offset_to_layout_shift_root_delta;
+  // offset caused by change of paint offset translations and scroll offset
+  // below the layout shift root. For more details, see
+  // renderer/core/layout/layout-shift-tracker-old-paint-offset.md.
   PhysicalOffset adjusted_old_paint_offset =
-      adjusted_old_transform_indifferent_paint_offset -
-      tree_builder_context.translation_2d_to_layout_shift_root_delta;
+      context.old_paint_offset -
+      tree_builder_context.current
+          .additional_offset_to_layout_shift_root_delta -
+      PhysicalOffset::FromFloatSizeRound(
+          tree_builder_context.translation_2d_to_layout_shift_root_delta +
+          tree_builder_context.current
+              .scroll_offset_to_layout_shift_root_delta);
   PhysicalOffset new_paint_offset = tree_builder_context.current.paint_offset;
 
   if (object.IsText()) {
@@ -229,8 +232,9 @@
     layout_shift_tracker.NotifyTextPrePaint(
         text, property_tree_state, old_starting_point, new_starting_point,
         adjusted_old_paint_offset,
-        adjusted_old_transform_indifferent_paint_offset, new_paint_offset,
-        logical_height);
+        tree_builder_context.translation_2d_to_layout_shift_root_delta,
+        tree_builder_context.current.scroll_offset_to_layout_shift_root_delta,
+        new_paint_offset, logical_height);
     return;
   }
 
@@ -288,7 +292,9 @@
   if (should_report_layout_shift) {
     layout_shift_tracker.NotifyBoxPrePaint(
         box, property_tree_state, old_rect, new_rect, adjusted_old_paint_offset,
-        adjusted_old_transform_indifferent_paint_offset, new_paint_offset);
+        tree_builder_context.translation_2d_to_layout_shift_root_delta,
+        tree_builder_context.current.scroll_offset_to_layout_shift_root_delta,
+        new_paint_offset);
   }
 }
 
@@ -368,9 +374,10 @@
         UpdateFromTreeBuilderContext(fragment_tree_builder_context, context);
         UpdateLayoutShiftTracking(object, fragment_tree_builder_context,
                                   context);
-        object.GetFrameView()
-            ->GetMobileFriendlinessChecker()
-            .NotifyInvalidatePaint(object);
+
+        if (auto* mf_checker =
+                object.GetFrameView()->GetMobileFriendlinessChecker())
+          mf_checker->NotifyInvalidatePaint(object);
       } else {
         context.old_paint_offset = fragment_data->PaintOffset();
       }
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painter.cc b/third_party/blink/renderer/core/paint/paint_layer_painter.cc
index eeee269..0219227 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painter.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_painter.cc
@@ -168,7 +168,7 @@
   // of the main frame.
   if (IsA<LayoutView>(layer.GetLayoutObject())) {
     const auto* frame = layer.GetLayoutObject().GetFrame();
-    if (frame && frame->IsMainFrame() && !frame->ClipsContent())
+    if (frame && !frame->ClipsContent())
       return true;
   }
   return false;
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 9a66acc..7ff6704 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -1045,7 +1045,7 @@
     }
     if (transform->IsIdentityOr2DTranslation()) {
       context_.translation_2d_to_layout_shift_root_delta +=
-          PhysicalOffset::FromFloatSizeRound(transform->Translation2D());
+          transform->Translation2D();
     }
   } else if (RuntimeEnabledFeatures::TransformInteropEnabled() &&
              !object_.IsAnonymous()) {
@@ -1988,6 +1988,12 @@
 void FragmentPaintPropertyTreeBuilder::UpdateScrollAndScrollTranslation() {
   DCHECK(properties_);
 
+  FloatSize old_scroll_offset;
+  if (const auto* old_scroll_translation = properties_->ScrollTranslation()) {
+    DCHECK(full_context_.was_layout_shift_root);
+    old_scroll_offset = old_scroll_translation->Translation2D();
+  }
+
   if (NeedsPaintPropertyUpdate()) {
     if (object_.IsBox() && To<LayoutBox>(object_).NeedsScrollNode(
                                full_context_.direct_compositing_reasons)) {
@@ -2138,12 +2144,17 @@
   if (properties_->Scroll())
     context_.current.scroll = properties_->Scroll();
 
-  if (properties_->ScrollTranslation()) {
-    context_.current.transform = properties_->ScrollTranslation();
+  if (const auto* scroll_translation = properties_->ScrollTranslation()) {
+    context_.current.transform = scroll_translation;
     // See comments for ScrollTranslation in object_paint_properties.h for the
     // reason of adding ScrollOrigin().
     context_.current.paint_offset +=
         PhysicalOffset(To<LayoutBox>(object_).ScrollOrigin());
+
+    // A scroller creates a layout shift root, so we just calculate one scroll
+    // offset delta without accumulation.
+    context_.current.scroll_offset_to_layout_shift_root_delta =
+        scroll_translation->Translation2D() - old_scroll_offset;
   }
 }
 
@@ -2702,10 +2713,13 @@
   // For LayoutView, additional_offset_to_layout_shift_root_delta applies to
   // neither itself nor descendants. For other layout shift roots, we clear the
   // delta at the end of UpdateForChildren() because the delta still applies to
-  // the object itself.
+  // the object itself. Same for translation_2d_to_layout_shift_delta and
+  // scroll_offset_to_layout_shift_root_delta.
   if (IsA<LayoutView>(object_)) {
     context_.current.additional_offset_to_layout_shift_root_delta =
-        context_.translation_2d_to_layout_shift_root_delta = PhysicalOffset();
+        PhysicalOffset();
+    context_.translation_2d_to_layout_shift_root_delta = FloatSize();
+    context_.current.scroll_offset_to_layout_shift_root_delta = FloatSize();
   }
 }
 
@@ -2740,7 +2754,11 @@
     // additional_offset_to_layout_shift_root_delta.
     context_.current.additional_offset_to_layout_shift_root_delta =
         context_.old_paint_offset - fragment_data_.PaintOffset();
-    context_.translation_2d_to_layout_shift_root_delta = PhysicalOffset();
+    context_.translation_2d_to_layout_shift_root_delta = FloatSize();
+    // Don't reset scroll_offset_to_layout_shift_root_delta if this object has
+    // scroll translation because we need to propagate the delta to descendants.
+    if (!properties_ || !properties_->ScrollTranslation())
+      context_.current.scroll_offset_to_layout_shift_root_delta = FloatSize();
   }
 
 #if DCHECK_IS_ON()
@@ -2779,7 +2797,7 @@
     if (const auto* transform = properties->Transform()) {
       if (transform->IsIdentityOr2DTranslation()) {
         context.translation_2d_to_layout_shift_root_delta -=
-            PhysicalOffset::FromFloatSizeRound(transform->Translation2D());
+            transform->Translation2D();
       }
     }
   }
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.h b/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
index 863c116b..510f53a0 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
@@ -65,6 +65,10 @@
     // layout shift root.
     PhysicalOffset additional_offset_to_layout_shift_root_delta;
 
+    // Similar to additional_offset_to_layout_shift_root_delta but for scroll
+    // offsets.
+    FloatSize scroll_offset_to_layout_shift_root_delta;
+
     // For paint invalidation optimization for subpixel movement under
     // composited layer. It's reset to zero if subpixel can't be propagated
     // thus the optimization is not applicable (e.g. when crossing a
@@ -154,7 +158,7 @@
 
   // The delta between the old and new accumulated offsets of 2d translation
   // transforms to the layout shift root.
-  PhysicalOffset translation_2d_to_layout_shift_root_delta;
+  FloatSize translation_2d_to_layout_shift_root_delta;
 };
 
 struct PaintPropertyTreeBuilderContext {
diff --git a/third_party/blink/renderer/core/paint/paint_timing.cc b/third_party/blink/renderer/core/paint/paint_timing.cc
index c77d38a..b5541a0 100644
--- a/third_party/blink/renderer/core/paint/paint_timing.cc
+++ b/third_party/blink/renderer/core/paint/paint_timing.cc
@@ -17,6 +17,7 @@
 #include "third_party/blink/renderer/core/loader/document_loader.h"
 #include "third_party/blink/renderer/core/loader/interactive_detector.h"
 #include "third_party/blink/renderer/core/loader/progress_tracker.h"
+#include "third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
@@ -252,6 +253,9 @@
 
   if (frame->GetFrameScheduler())
     frame->GetFrameScheduler()->OnFirstContentfulPaint();
+
+  if (auto* mf_checker = frame->View()->GetMobileFriendlinessChecker())
+    mf_checker->NotifyFirstContentfulPaint();
 }
 
 void PaintTiming::RegisterNotifyPresentationTime(PaintEvent event) {
diff --git a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
index d6de62c..1f4ffc1 100644
--- a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
+++ b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
@@ -24,7 +24,6 @@
 #include "third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
-#include "third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
@@ -311,7 +310,6 @@
   }
 
   frame_view.GetLayoutShiftTracker().NotifyPrePaintFinished();
-  frame_view.GetMobileFriendlinessChecker().NotifyPrePaintFinished();
   context_storage_.pop_back();
 }
 
diff --git a/third_party/blink/renderer/core/scroll/build.gni b/third_party/blink/renderer/core/scroll/build.gni
index 72f4e36..89965e2 100644
--- a/third_party/blink/renderer/core/scroll/build.gni
+++ b/third_party/blink/renderer/core/scroll/build.gni
@@ -10,6 +10,8 @@
   "programmatic_scroll_animator.h",
   "scroll_alignment.cc",
   "scroll_alignment.h",
+  "scroll_animator.cc",
+  "scroll_animator.h",
   "scroll_animator_base.cc",
   "scroll_animator_base.h",
   "scroll_animator_compositor_coordinator.cc",
@@ -22,6 +24,7 @@
   "scrollable_area.h",
   "scrollbar.cc",
   "scrollbar.h",
+  "mac_scrollbar_animator.h",
   "scrollbar_layer_delegate.cc",
   "scrollbar_layer_delegate.h",
   "scrollbar_theme.cc",
@@ -41,17 +44,12 @@
 
 if (is_mac) {
   blink_core_sources_scroll += [
-    "scroll_animator_mac.h",
-    "scroll_animator_mac.mm",
+    "mac_scrollbar_animator_impl.h",
+    "mac_scrollbar_animator_impl.mm",
     "scrollbar_theme_mac.h",
     "scrollbar_theme_mac.mm",
     "web_scrollbar_theme.mm",
   ]
-} else {
-  blink_core_sources_scroll += [
-    "scroll_animator.cc",
-    "scroll_animator.h",
-  ]
 }
 
 if (use_aura) {
diff --git a/third_party/blink/renderer/core/scroll/mac_scrollbar_animator.h b/third_party/blink/renderer/core/scroll/mac_scrollbar_animator.h
new file mode 100644
index 0000000..6d5f918c
--- /dev/null
+++ b/third_party/blink/renderer/core/scroll/mac_scrollbar_animator.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 THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_MAC_SCROLLBAR_ANIMATOR_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_MAC_SCROLLBAR_ANIMATOR_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/scroll/scroll_types.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+
+namespace blink {
+class ScrollableArea;
+class Scrollbar;
+
+// This is a base class for MacScrollbarAnimatorImpl. This is required because
+// mac_scrollbar_animator_impl.h has some #include that can't be included in
+// most platform-agnostic code.
+class CORE_EXPORT MacScrollbarAnimator
+    : public GarbageCollected<MacScrollbarAnimator> {
+ public:
+  // Create method that returns a MacScrollAnimatorImpl when on Mac, and isn't
+  // implemented on other platforms. This is a workaround to instatiating
+  // ScrollAnimatorMac directly on it's callers (e.g. ScrollableArea).
+  static MacScrollbarAnimator* Create(ScrollableArea*);
+  virtual void Trace(Visitor* visitor) const {}
+
+  virtual void ContentAreaWillPaint() const = 0;
+  virtual void MouseEnteredContentArea() const = 0;
+  virtual void MouseExitedContentArea() const = 0;
+  virtual void MouseMovedInContentArea() const = 0;
+  virtual void MouseEnteredScrollbar(Scrollbar&) const = 0;
+  virtual void MouseExitedScrollbar(Scrollbar&) const = 0;
+  virtual void ContentsResized() const = 0;
+
+  virtual void DidAddVerticalScrollbar(Scrollbar&) = 0;
+  virtual void WillRemoveVerticalScrollbar(Scrollbar&) = 0;
+  virtual void DidAddHorizontalScrollbar(Scrollbar&) = 0;
+  virtual void WillRemoveHorizontalScrollbar(Scrollbar&) = 0;
+
+  virtual bool SetScrollbarsVisibleForTesting(bool) = 0;
+
+  virtual void DidChangeUserVisibleScrollOffset(const ScrollOffset&) = 0;
+
+  virtual void UpdateScrollerStyle() = 0;
+
+  virtual bool ScrollbarPaintTimerIsActive() const = 0;
+  virtual void StartScrollbarPaintTimer() = 0;
+  virtual void StopScrollbarPaintTimer() = 0;
+
+  virtual void Dispose() = 0;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_MAC_SCROLLBAR_ANIMATOR_H_
diff --git a/third_party/blink/renderer/core/scroll/mac_scrollbar_animator_impl.h b/third_party/blink/renderer/core/scroll/mac_scrollbar_animator_impl.h
new file mode 100644
index 0000000..ed5e7526
--- /dev/null
+++ b/third_party/blink/renderer/core/scroll/mac_scrollbar_animator_impl.h
@@ -0,0 +1,134 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_MAC_SCROLLBAR_ANIMATOR_IMPL_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_MAC_SCROLLBAR_ANIMATOR_IMPL_H_
+
+#include <memory>
+
+#include "base/mac/scoped_nsobject.h"
+#include "base/single_thread_task_runner.h"
+#include "third_party/blink/renderer/core/scroll/mac_scrollbar_animator.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/platform/geometry/float_point.h"
+#include "third_party/blink/renderer/platform/geometry/float_size.h"
+#include "third_party/blink/renderer/platform/geometry/int_rect.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
+#include "third_party/blink/renderer/platform/timer.h"
+
+@class BlinkScrollbarPainterControllerDelegate;
+@class BlinkScrollbarPainterDelegate;
+
+typedef id ScrollbarPainterController;
+typedef id ScrollbarPainter;
+
+namespace blink {
+// This class handles scrollbar opacity animations by delegating to native
+// Cocoa APIs (.mm).
+// It was created with the goal of solving (crbug.com/682209), but we still
+// need to replace the Cocoa APIs calls by platform-agnostic code.
+//
+// The animations handled are:
+// - knob alpha : thumb transparency animation
+// - track alpha : track transparency animation
+// - ui state transition
+// - expansion transition
+// (these are enumerated by |FeatureToAnimate|)
+//
+// All these animation are theme-related, it means that they're specific to
+// the ScrollbarThemeMac theme, and use the Cocoa private APIs to drive that
+// animation.
+//
+// The objective-c classes defined on the .mm file are used to glue our blink
+// code with the Cocoa APIs for animation.
+//
+// - |BlinkScrollbarPartAnimation|: animates the properties of a
+// |ScrollbarPainter| (|NSScrollerImp|) object based on a |FeatureToAnimate|.
+// - |BlinkScrollbarPartAnimationTimer|: Implements the curve that maps the time
+// elapsed in the animation to the animation progress.
+// - |BlinkScrollbarPainterControllerDelegate|: Delegates tasks from a
+// |ScrollbarPainterController| (|NSScrollerImpPair|) to the |ScrollableArea|
+// where the ScrollbarPainter is painting
+// - |BlinkScrollbarPainterDelegate|: Delegates the creation and running of all
+// 4 |BlinkScrollbarPartAnimation| on a |ScrollbarPainter|.
+//
+//
+// The usage of these classes follow:
+//
+// The "scrollbar painter controller" calls back into Blink via
+// BlinkScrollbarPainterControllerDelegate.
+//
+// The "scrollbar painter" calls back into Blink via
+// BlinkScrollbarPainterDelegate.  The scrollbar painter is registered
+// with ScrollbarThemeMac, so that the ScrollbarTheme painting APIs can call
+// into it.
+//
+// The scrollbar painter initiates an overlay scrollbar fade-out animation by
+// calling animateKnobAlphaTo on the delegate.  This starts a timer inside the
+// BlinkScrollbarPartAnimationTimer.  Each tick evaluates a cubic bezier
+// function to obtain the current opacity, which is stored in the scrollbar
+// painter with setKnobAlpha.
+//
+// If the scroller is composited, the opacity value stored on the scrollbar
+// painter is subsequently read out through ScrollbarThemeMac::ThumbOpacity and
+// plumbed into PaintedScrollbarLayerImpl::thumb_opacity_.
+class PLATFORM_EXPORT MacScrollbarAnimatorImpl : public MacScrollbarAnimator {
+ public:
+  MacScrollbarAnimatorImpl(ScrollableArea*);
+  virtual ~MacScrollbarAnimatorImpl() = default;
+
+  bool needs_scroller_style_update_ = false;
+  ScrollOffset content_area_scrolled_timer_scroll_delta_;
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+  TaskHandle initial_scrollbar_paint_task_handle_;
+  TaskHandle send_content_area_scrolled_task_handle_;
+
+  // MacScrollbarAnimator overrides
+  void ContentAreaWillPaint() const override;
+  void MouseEnteredContentArea() const override;
+  void MouseExitedContentArea() const override;
+  void MouseMovedInContentArea() const override;
+  void MouseEnteredScrollbar(Scrollbar&) const override;
+  void MouseExitedScrollbar(Scrollbar&) const override;
+  void ContentsResized() const override;
+  void DidAddVerticalScrollbar(Scrollbar&) override;
+  void WillRemoveVerticalScrollbar(Scrollbar&) override;
+  void DidAddHorizontalScrollbar(Scrollbar&) override;
+  void WillRemoveHorizontalScrollbar(Scrollbar&) override;
+  bool SetScrollbarsVisibleForTesting(bool) override;
+  void DidChangeUserVisibleScrollOffset(
+      const ScrollOffset& offset_delta) override;
+
+  void UpdateScrollerStyle() override;
+
+  void InitialScrollbarPaintTask();
+  void SendContentAreaScrolledTask();
+
+  bool ScrollbarPaintTimerIsActive() const override;
+  void StartScrollbarPaintTimer() override;
+  void StopScrollbarPaintTimer() override;
+
+  void Dispose() override;
+
+  void Trace(Visitor* visitor) const override {
+    visitor->Trace(scrollable_area_);
+    MacScrollbarAnimator::Trace(visitor);
+  }
+
+ protected:
+  base::scoped_nsobject<ScrollbarPainterController>
+      scrollbar_painter_controller_;
+  base::scoped_nsobject<BlinkScrollbarPainterControllerDelegate>
+      scrollbar_painter_controller_delegate_;
+  base::scoped_nsobject<BlinkScrollbarPainterDelegate>
+      horizontal_scrollbar_painter_delegate_;
+  base::scoped_nsobject<BlinkScrollbarPainterDelegate>
+      vertical_scrollbar_painter_delegate_;
+
+  Member<ScrollableArea> scrollable_area_;
+};
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_MAC_SCROLLBAR_ANIMATOR_IMPL_H_
diff --git a/third_party/blink/renderer/core/scroll/scroll_animator_mac.mm b/third_party/blink/renderer/core/scroll/mac_scrollbar_animator_impl.mm
similarity index 69%
rename from third_party/blink/renderer/core/scroll/scroll_animator_mac.mm
rename to third_party/blink/renderer/core/scroll/mac_scrollbar_animator_impl.mm
index 29419d0..f703eac 100644
--- a/third_party/blink/renderer/core/scroll/scroll_animator_mac.mm
+++ b/third_party/blink/renderer/core/scroll/mac_scrollbar_animator_impl.mm
@@ -1,51 +1,21 @@
-/*
- * Copyright (C) 2010, 2011 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// 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 "third_party/blink/renderer/core/scroll/scroll_animator_mac.h"
+#include "third_party/blink/renderer/core/scroll/mac_scrollbar_animator_impl.h"
 
 #import <AppKit/AppKit.h>
 
-#include <memory>
-#include "base/mac/scoped_cftyperef.h"
-#include "base/memory/scoped_policy.h"
+#include "base/mac/scoped_nsobject.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/core/scroll/ns_scroller_imp_details.h"
-#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator.h"
 #include "third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h"
 #include "third_party/blink/renderer/platform/animation/timing_function.h"
-#include "third_party/blink/renderer/platform/geometry/float_rect.h"
-#include "third_party/blink/renderer/platform/geometry/int_rect.h"
-#include "third_party/blink/renderer/platform/heap/impl/persistent.h"
 #include "third_party/blink/renderer/platform/mac/block_exceptions.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
-#include "third_party/blink/renderer/platform/timer.h"
-#include "third_party/blink/renderer/platform/wtf/math_extras.h"
 
 namespace {
-
 bool SupportsUIStateTransitionProgress() {
   // FIXME: This is temporary until all platforms that support ScrollbarPainter
   // support this part of the API.
@@ -87,96 +57,8 @@
 
 }  // namespace
 
-@interface NSObject (ScrollAnimationHelperDetails)
-- (id)initWithDelegate:(id)delegate;
-- (void)_stopRun;
-- (BOOL)_isAnimating;
-- (NSPoint)targetOrigin;
-- (CGFloat)_progress;
-@end
-
-@interface BlinkScrollAnimationHelperDelegate : NSObject {
-  blink::ScrollAnimatorMac* _animator;
-}
-- (id)initWithScrollAnimator:(blink::ScrollAnimatorMac*)scrollAnimator;
-@end
-
-static NSSize abs(NSSize size) {
-  NSSize finalSize = size;
-  if (finalSize.width < 0)
-    finalSize.width = -finalSize.width;
-  if (finalSize.height < 0)
-    finalSize.height = -finalSize.height;
-  return finalSize;
-}
-
-@implementation BlinkScrollAnimationHelperDelegate
-
-- (id)initWithScrollAnimator:(blink::ScrollAnimatorMac*)scrollAnimator {
-  self = [super init];
-  if (!self)
-    return nil;
-
-  _animator = scrollAnimator;
-  return self;
-}
-
-- (void)invalidate {
-  _animator = 0;
-}
-
-- (NSRect)bounds {
-  if (!_animator)
-    return NSZeroRect;
-
-  blink::ScrollOffset currentOffset = _animator->CurrentOffset();
-  return NSMakeRect(currentOffset.Width(), currentOffset.Height(), 0, 0);
-}
-
-- (void)_immediateScrollToPoint:(NSPoint)newPosition {
-  if (!_animator)
-    return;
-  _animator->ImmediateScrollToOffsetForScrollAnimation(
-      blink::ToScrollOffset(newPosition));
-}
-
-- (NSPoint)_pixelAlignProposedScrollPosition:(NSPoint)newOrigin {
-  return newOrigin;
-}
-
-- (NSSize)convertSizeToBase:(NSSize)size {
-  return abs(size);
-}
-
-- (NSSize)convertSizeFromBase:(NSSize)size {
-  return abs(size);
-}
-
-- (NSSize)convertSizeToBacking:(NSSize)size {
-  return abs(size);
-}
-
-- (NSSize)convertSizeFromBacking:(NSSize)size {
-  return abs(size);
-}
-
-- (id)superview {
-  return nil;
-}
-
-- (id)documentView {
-  return nil;
-}
-
-- (id)window {
-  return nil;
-}
-
-- (void)_recursiveRecomputeToolTips {
-}
-
-@end
-
+// This class is a delegator of ScrollbarPainterController to ScrollableArea
+// that has the scrollbars of a ScrollbarPainter.
 @interface BlinkScrollbarPainterControllerDelegate : NSObject {
   blink::ScrollableArea* _scrollableArea;
 }
@@ -249,7 +131,7 @@
   if (!_scrollableArea->ScrollbarsCanBeActive())
     return;
 
-  _scrollableArea->GetScrollAnimator().ContentAreaWillPaint();
+  _scrollableArea->ContentAreaWillPaint();
 }
 
 - (void)scrollerImpPair:(id)scrollerImpPair
@@ -269,8 +151,7 @@
 
   [scrollerImpPair setScrollerStyle:newRecommendedScrollerStyle];
 
-  static_cast<blink::ScrollAnimatorMac&>(_scrollableArea->GetScrollAnimator())
-      .UpdateScrollerStyle();
+  _scrollableArea->GetMacScrollbarAnimator()->UpdateScrollerStyle();
 }
 
 @end
@@ -283,9 +164,7 @@
 };
 
 @class BlinkScrollbarPartAnimation;
-
 namespace blink {
-
 // This class is used to drive the animation timer for
 // BlinkScrollbarPartAnimation
 // objects. This is used instead of NSAnimation because CoreAnimation
@@ -342,6 +221,8 @@
 
 }  // namespace blink
 
+// This class handles the animation of a |_featureToAnimate| part of
+// |_scrollbar|.
 @interface BlinkScrollbarPartAnimation : NSObject {
   // TODO(crbug.com/1185337): This WeakPersistent was added as a speculative
   // because fix for crbug.com/1183276.
@@ -451,12 +332,13 @@
   BEGIN_BLOCK_OBJC_EXCEPTIONS;
   [self stopAnimation];
   END_BLOCK_OBJC_EXCEPTIONS;
-  _scrollbar = 0;
+  _scrollbar = nullptr;
 }
-
 @end
 
-@interface BlinkScrollbarPainterDelegate : NSObject<NSAnimationDelegate> {
+// This class is a delegator of ScrollbarPainter to the 4 types of animation
+// it can run. The animations are run through BlinkScrollbarPartAnimation.
+@interface BlinkScrollbarPainterDelegate : NSObject <NSAnimationDelegate> {
   blink::Scrollbar* _scrollbar;
   scoped_refptr<base::SingleThreadTaskRunner> _taskRunner;
 
@@ -502,8 +384,8 @@
   END_BLOCK_OBJC_EXCEPTIONS;
 }
 
-- (blink::ScrollAnimatorMac&)scrollAnimator {
-  return static_cast<blink::ScrollAnimatorMac&>(
+- (blink::ScrollAnimator&)scrollAnimator {
+  return static_cast<blink::ScrollAnimator&>(
       _scrollbar->GetScrollableArea()->GetScrollAnimator());
 }
 
@@ -532,22 +414,26 @@
                        part:(blink::ScrollbarPart)part
              animateAlphaTo:(CGFloat)newAlpha
                    duration:(NSTimeInterval)duration {
+  blink::MacScrollbarAnimator* scrollbar_animator =
+      _scrollbar->GetScrollableArea()->GetMacScrollbarAnimator();
+  DCHECK(scrollbar_animator);
+
   // If the user has scrolled the page, then the scrollbars must be animated
   // here.
   // This overrides the early returns.
   bool mustAnimate = [self scrollAnimator].HaveScrolledSincePageLoad();
 
-  if ([self scrollAnimator].ScrollbarPaintTimerIsActive() && !mustAnimate)
+  if (scrollbar_animator->ScrollbarPaintTimerIsActive() && !mustAnimate)
     return;
 
   if (_scrollbar->GetScrollableArea()->ShouldSuspendScrollAnimations() &&
       !mustAnimate) {
-    [self scrollAnimator].StartScrollbarPaintTimer();
+    scrollbar_animator->StartScrollbarPaintTimer();
     return;
   }
 
   // At this point, we are definitely going to animate now, so stop the timer.
-  [self scrollAnimator].StopScrollbarPaintTimer();
+  scrollbar_animator->StopScrollbarPaintTimer();
 
   // If we are currently animating, stop
   if (scrollbarPartAnimation) {
@@ -703,26 +589,13 @@
   [_expansionTransitionAnimation invalidate];
   END_BLOCK_OBJC_EXCEPTIONS;
 }
-
 @end
 
 namespace blink {
-
-ScrollAnimatorBase* ScrollAnimatorBase::Create(
-    blink::ScrollableArea* scrollable_area) {
-  return MakeGarbageCollected<ScrollAnimatorMac>(scrollable_area);
-}
-
-ScrollAnimatorMac::ScrollAnimatorMac(blink::ScrollableArea* scrollable_area)
-    : ScrollAnimatorBase(scrollable_area),
-      task_runner_(scrollable_area->GetCompositorTaskRunner()),
-      have_scrolled_since_page_load_(false),
-      needs_scroller_style_update_(false) {
-  scroll_animation_helper_delegate_.reset(
-      [[BlinkScrollAnimationHelperDelegate alloc] initWithScrollAnimator:this]);
-  scroll_animation_helper_.reset([[NSClassFromString(@"NSScrollAnimationHelper")
-      alloc] initWithDelegate:scroll_animation_helper_delegate_]);
-
+MacScrollbarAnimatorImpl::MacScrollbarAnimatorImpl(
+    ScrollableArea* scrollable_area)
+    : task_runner_(scrollable_area->GetCompositorTaskRunner()),
+      scrollable_area_(scrollable_area) {
   scrollbar_painter_controller_delegate_.reset(
       [[BlinkScrollbarPainterControllerDelegate alloc]
           initWithScrollableArea:scrollable_area]);
@@ -736,9 +609,7 @@
       setScrollerStyle:ScrollbarThemeMac::RecommendedScrollerStyle()];
 }
 
-ScrollAnimatorMac::~ScrollAnimatorMac() {}
-
-void ScrollAnimatorMac::Dispose() {
+void MacScrollbarAnimatorImpl::Dispose() {
   BEGIN_BLOCK_OBJC_EXCEPTIONS;
   ScrollbarPainter horizontal_scrollbar_painter =
       [scrollbar_painter_controller_ horizontalScrollerImp];
@@ -752,122 +623,36 @@
   [scrollbar_painter_controller_ setDelegate:nil];
   [horizontal_scrollbar_painter_delegate_ invalidate];
   [vertical_scrollbar_painter_delegate_ invalidate];
-  [scroll_animation_helper_delegate_ invalidate];
   END_BLOCK_OBJC_EXCEPTIONS;
 
   initial_scrollbar_paint_task_handle_.Cancel();
   send_content_area_scrolled_task_handle_.Cancel();
 }
 
-ScrollResult ScrollAnimatorMac::UserScroll(
-    ScrollGranularity granularity,
-    const ScrollOffset& delta,
-    ScrollableArea::ScrollCallback on_finish) {
-  have_scrolled_since_page_load_ = true;
-
-  if (!scrollable_area_->ScrollAnimatorEnabled() ||
-      granularity == ScrollGranularity::kScrollByPixel ||
-      granularity == ScrollGranularity::kScrollByPrecisePixel) {
-    return ScrollAnimatorBase::UserScroll(granularity, delta,
-                                          std::move(on_finish));
-  }
-
-  // TODO(lanwei): we should find when the animation finishes and run the
-  // callback after the animation finishes, see https://crbug.com/967842.
-  if (on_finish)
-    std::move(on_finish).Run();
-
-  ScrollOffset consumed_delta = ComputeDeltaToConsume(delta);
-  ScrollOffset new_offset = current_offset_ + consumed_delta;
-  if (current_offset_ == new_offset)
-    return ScrollResult();
-
-  // Prevent clobbering an existing animation on an unscrolled axis.
-  if ([scroll_animation_helper_ _isAnimating]) {
-    NSPoint target_origin = [scroll_animation_helper_ targetOrigin];
-    if (!delta.Width())
-      new_offset.SetWidth(target_origin.x);
-    if (!delta.Height())
-      new_offset.SetHeight(target_origin.y);
-  }
-
-  NSPoint new_point = NSMakePoint(new_offset.Width(), new_offset.Height());
-  [scroll_animation_helper_ scrollToPoint:new_point];
-
-  // TODO(bokan): This has different semantics on ScrollResult than
-  // ScrollAnimator,
-  // which only returns unused delta if there's no animation and we don't start
-  // one.
-  return ScrollResult(consumed_delta.Width(), consumed_delta.Height(),
-                      delta.Width() - consumed_delta.Width(),
-                      delta.Height() - consumed_delta.Height());
-}
-
-void ScrollAnimatorMac::ScrollToOffsetWithoutAnimation(
-    const ScrollOffset& offset) {
-  [scroll_animation_helper_ _stopRun];
-  ImmediateScrollTo(offset);
-}
-
-ScrollOffset ScrollAnimatorMac::AdjustScrollOffsetIfNecessary(
-    const ScrollOffset& offset) const {
-  ScrollOffset min_offset = scrollable_area_->MinimumScrollOffset();
-  ScrollOffset max_offset = scrollable_area_->MaximumScrollOffset();
-
-  float new_x = clampTo<float, float>(offset.Width(), min_offset.Width(),
-                                      max_offset.Width());
-  float new_y = clampTo<float, float>(offset.Height(), min_offset.Height(),
-                                      max_offset.Height());
-
-  return ScrollOffset(new_x, new_y);
-}
-
-void ScrollAnimatorMac::ImmediateScrollTo(const ScrollOffset& new_offset) {
-  ScrollOffset adjusted_offset = AdjustScrollOffsetIfNecessary(new_offset);
-
-  bool offset_changed = adjusted_offset != current_offset_;
-  if (!offset_changed)
-    return;
-
-  ScrollOffset delta = adjusted_offset - current_offset_;
-
-  current_offset_ = adjusted_offset;
-  NotifyContentAreaScrolled(delta, mojom::blink::ScrollType::kUser);
-  NotifyOffsetChanged();
-}
-
-void ScrollAnimatorMac::ImmediateScrollToOffsetForScrollAnimation(
-    const ScrollOffset& new_offset) {
-  DCHECK(scroll_animation_helper_);
-  ImmediateScrollTo(new_offset);
-}
-
-void ScrollAnimatorMac::ContentAreaWillPaint() const {
-  if (!GetScrollableArea()->ScrollbarsCanBeActive())
+void MacScrollbarAnimatorImpl::ContentAreaWillPaint() const {
+  if (!scrollable_area_->ScrollbarsCanBeActive())
     return;
   [scrollbar_painter_controller_ contentAreaWillDraw];
 }
-
-void ScrollAnimatorMac::MouseEnteredContentArea() const {
-  if (!GetScrollableArea()->ScrollbarsCanBeActive())
+void MacScrollbarAnimatorImpl::MouseEnteredContentArea() const {
+  if (!scrollable_area_->ScrollbarsCanBeActive())
     return;
   [scrollbar_painter_controller_ mouseEnteredContentArea];
 }
-
-void ScrollAnimatorMac::MouseExitedContentArea() const {
-  if (!GetScrollableArea()->ScrollbarsCanBeActive())
+void MacScrollbarAnimatorImpl::MouseExitedContentArea() const {
+  if (!scrollable_area_->ScrollbarsCanBeActive())
     return;
   [scrollbar_painter_controller_ mouseExitedContentArea];
 }
-
-void ScrollAnimatorMac::MouseMovedInContentArea() const {
-  if (!GetScrollableArea()->ScrollbarsCanBeActive())
+void MacScrollbarAnimatorImpl::MouseMovedInContentArea() const {
+  if (!scrollable_area_->ScrollbarsCanBeActive())
     return;
   [scrollbar_painter_controller_ mouseMovedInContentArea];
 }
 
-void ScrollAnimatorMac::MouseEnteredScrollbar(Scrollbar& scrollbar) const {
-  if (!GetScrollableArea()->ScrollbarsCanBeActive())
+void MacScrollbarAnimatorImpl::MouseEnteredScrollbar(
+    Scrollbar& scrollbar) const {
+  if (!scrollable_area_->ScrollbarsCanBeActive())
     return;
 
   if (!SupportsUIStateTransitionProgress())
@@ -876,8 +661,9 @@
     [painter mouseEnteredScroller];
 }
 
-void ScrollAnimatorMac::MouseExitedScrollbar(Scrollbar& scrollbar) const {
-  if (!GetScrollableArea()->ScrollbarsCanBeActive())
+void MacScrollbarAnimatorImpl::MouseExitedScrollbar(
+    Scrollbar& scrollbar) const {
+  if (!scrollable_area_->ScrollbarsCanBeActive())
     return;
 
   if (!SupportsUIStateTransitionProgress())
@@ -886,29 +672,13 @@
     [painter mouseExitedScroller];
 }
 
-void ScrollAnimatorMac::ContentsResized() const {
-  if (!GetScrollableArea()->ScrollbarsCanBeActive())
+void MacScrollbarAnimatorImpl::ContentsResized() const {
+  if (!scrollable_area_->ScrollbarsCanBeActive())
     return;
   [scrollbar_painter_controller_ contentAreaDidResize];
 }
 
-void ScrollAnimatorMac::ContentAreaDidShow() const {
-  if (!GetScrollableArea()->ScrollbarsCanBeActive())
-    return;
-  [scrollbar_painter_controller_ windowOrderedIn];
-}
-
-void ScrollAnimatorMac::ContentAreaDidHide() const {
-  if (!GetScrollableArea()->ScrollbarsCanBeActive())
-    return;
-  [scrollbar_painter_controller_ windowOrderedOut];
-}
-
-void ScrollAnimatorMac::FinishCurrentScrollAnimations() {
-  [scrollbar_painter_controller_ hideOverlayScrollers];
-}
-
-void ScrollAnimatorMac::DidAddVerticalScrollbar(Scrollbar& scrollbar) {
+void MacScrollbarAnimatorImpl::DidAddVerticalScrollbar(Scrollbar& scrollbar) {
   ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar);
   if (!painter)
     return;
@@ -922,7 +692,8 @@
   [scrollbar_painter_controller_ setVerticalScrollerImp:painter];
 }
 
-void ScrollAnimatorMac::WillRemoveVerticalScrollbar(Scrollbar& scrollbar) {
+void MacScrollbarAnimatorImpl::WillRemoveVerticalScrollbar(
+    Scrollbar& scrollbar) {
   ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar);
   DCHECK_EQ([scrollbar_painter_controller_ verticalScrollerImp], painter);
 
@@ -935,7 +706,7 @@
   [scrollbar_painter_controller_ setVerticalScrollerImp:nil];
 }
 
-void ScrollAnimatorMac::DidAddHorizontalScrollbar(Scrollbar& scrollbar) {
+void MacScrollbarAnimatorImpl::DidAddHorizontalScrollbar(Scrollbar& scrollbar) {
   ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar);
   if (!painter)
     return;
@@ -949,7 +720,8 @@
   [scrollbar_painter_controller_ setHorizontalScrollerImp:painter];
 }
 
-void ScrollAnimatorMac::WillRemoveHorizontalScrollbar(Scrollbar& scrollbar) {
+void MacScrollbarAnimatorImpl::WillRemoveHorizontalScrollbar(
+    Scrollbar& scrollbar) {
   ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar);
   DCHECK_EQ([scrollbar_painter_controller_ horizontalScrollerImp], painter);
 
@@ -962,20 +734,7 @@
   [scrollbar_painter_controller_ setHorizontalScrollerImp:nil];
 }
 
-void ScrollAnimatorMac::NotifyContentAreaScrolled(
-    const ScrollOffset& delta,
-    mojom::blink::ScrollType scrollType) {
-  // This function is called when a page is going into the page cache, but the
-  // page
-  // isn't really scrolling in that case. We should only pass the message on to
-  // the
-  // ScrollbarPainterController when we're really scrolling on an active page.
-  if (IsExplicitScrollType(scrollType) &&
-      GetScrollableArea()->ScrollbarsCanBeActive())
-    SendContentAreaScrolledSoon(delta);
-}
-
-bool ScrollAnimatorMac::SetScrollbarsVisibleForTesting(bool show) {
+bool MacScrollbarAnimatorImpl::SetScrollbarsVisibleForTesting(bool show) {
   if (show)
     [scrollbar_painter_controller_ flashScrollers];
   else
@@ -986,28 +745,18 @@
   return true;
 }
 
-void ScrollAnimatorMac::CancelAnimation() {
-  [scroll_animation_helper_ _stopRun];
-  have_scrolled_since_page_load_ = false;
-}
-
-void ScrollAnimatorMac::UpdateScrollerStyle() {
-  if (!GetScrollableArea()->ScrollbarsCanBeActive()) {
-    needs_scroller_style_update_ = true;
+void MacScrollbarAnimatorImpl::UpdateScrollerStyle() {
+  if (!scrollable_area_->ScrollbarsCanBeActive())
     return;
-  }
 
   blink::ScrollbarThemeMac* mac_theme =
       MacOverlayScrollbarTheme(scrollable_area_->GetPageScrollbarTheme());
-  if (!mac_theme) {
-    needs_scroller_style_update_ = false;
+  if (!mac_theme)
     return;
-  }
 
   NSScrollerStyle new_style = [scrollbar_painter_controller_ scrollerStyle];
 
-  if (Scrollbar* vertical_scrollbar =
-          GetScrollableArea()->VerticalScrollbar()) {
+  if (Scrollbar* vertical_scrollbar = scrollable_area_->VerticalScrollbar()) {
     vertical_scrollbar->SetNeedsPaintInvalidation(kAllParts);
 
     ScrollbarPainter old_vertical_painter =
@@ -1024,17 +773,15 @@
                                          new_vertical_painter);
 
     // The different scrollbar styles have different thicknesses, so we must
-    // re-set the
-    // frameRect to the new thickness, and the re-layout below will ensure the
-    // offset
-    // and length are properly updated.
+    // re-set the frameRect to the new thickness, and the re-layout below will
+    // ensure the offset and length are properly updated.
     int thickness =
         mac_theme->ScrollbarThickness(vertical_scrollbar->ScaleFromDIP());
     vertical_scrollbar->SetFrameRect(IntRect(0, 0, thickness, thickness));
   }
 
   if (Scrollbar* horizontal_scrollbar =
-          GetScrollableArea()->HorizontalScrollbar()) {
+          scrollable_area_->HorizontalScrollbar()) {
     horizontal_scrollbar->SetNeedsPaintInvalidation(kAllParts);
 
     ScrollbarPainter old_horizontal_painter =
@@ -1061,36 +808,27 @@
         mac_theme->ScrollbarThickness(horizontal_scrollbar->ScaleFromDIP());
     horizontal_scrollbar->SetFrameRect(IntRect(0, 0, thickness, thickness));
   }
-
-  // If m_needsScrollerStyleUpdate is true, then the page is restoring from the
-  // page cache, and
-  // a relayout will happen on its own. Otherwise, we must initiate a re-layout
-  // ourselves.
-  if (!needs_scroller_style_update_)
-    GetScrollableArea()->ScrollbarStyleChanged();
-
-  needs_scroller_style_update_ = false;
 }
 
-void ScrollAnimatorMac::StartScrollbarPaintTimer() {
+void MacScrollbarAnimatorImpl::StartScrollbarPaintTimer() {
   // Post a task with 1 ms delay to give a chance to run other immediate tasks
   // that may cancel this.
   initial_scrollbar_paint_task_handle_ = PostDelayedCancellableTask(
       *task_runner_, FROM_HERE,
-      WTF::Bind(&ScrollAnimatorMac::InitialScrollbarPaintTask,
+      WTF::Bind(&MacScrollbarAnimatorImpl::InitialScrollbarPaintTask,
                 WrapWeakPersistent(this)),
       base::TimeDelta::FromMilliseconds(1));
 }
 
-bool ScrollAnimatorMac::ScrollbarPaintTimerIsActive() const {
+bool MacScrollbarAnimatorImpl::ScrollbarPaintTimerIsActive() const {
   return initial_scrollbar_paint_task_handle_.IsActive();
 }
 
-void ScrollAnimatorMac::StopScrollbarPaintTimer() {
+void MacScrollbarAnimatorImpl::StopScrollbarPaintTimer() {
   initial_scrollbar_paint_task_handle_.Cancel();
 }
 
-void ScrollAnimatorMac::InitialScrollbarPaintTask() {
+void MacScrollbarAnimatorImpl::InitialScrollbarPaintTask() {
   // To force the scrollbars to flash, we have to call hide first. Otherwise,
   // the ScrollbarPainterController
   // might think that the scrollbars are already showing and bail early.
@@ -1098,18 +836,19 @@
   [scrollbar_painter_controller_ flashScrollers];
 }
 
-void ScrollAnimatorMac::SendContentAreaScrolledSoon(const ScrollOffset& delta) {
+void MacScrollbarAnimatorImpl::DidChangeUserVisibleScrollOffset(
+    const ScrollOffset& delta) {
   content_area_scrolled_timer_scroll_delta_ = delta;
 
   if (send_content_area_scrolled_task_handle_.IsActive())
     return;
   send_content_area_scrolled_task_handle_ = PostCancellableTask(
       *task_runner_, FROM_HERE,
-      WTF::Bind(&ScrollAnimatorMac::SendContentAreaScrolledTask,
+      WTF::Bind(&MacScrollbarAnimatorImpl::SendContentAreaScrolledTask,
                 WrapWeakPersistent(this)));
 }
 
-void ScrollAnimatorMac::SendContentAreaScrolledTask() {
+void MacScrollbarAnimatorImpl::SendContentAreaScrolledTask() {
   if (SupportsContentAreaScrolledInDirection()) {
     [scrollbar_painter_controller_
         contentAreaScrolledInDirection:
@@ -1120,4 +859,9 @@
     [scrollbar_painter_controller_ contentAreaScrolled];
 }
 
+MacScrollbarAnimator* MacScrollbarAnimator::Create(
+    ScrollableArea* scrollable_area) {
+  return MakeGarbageCollected<MacScrollbarAnimatorImpl>(
+      const_cast<ScrollableArea*>(scrollable_area));
+}
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/scroll/scroll_animator.cc b/third_party/blink/renderer/core/scroll/scroll_animator.cc
index 39208df..b246526 100644
--- a/third_party/blink/renderer/core/scroll/scroll_animator.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_animator.cc
@@ -101,10 +101,20 @@
   // scroll, the callback is invoked immediately without being stored.
   DCHECK(HasRunningAnimation() || on_finish_.is_null());
 
+#if defined(OS_MAC)
+  have_scrolled_since_page_load_ = true;
+#endif
+
   base::ScopedClosureRunner run_on_return(std::move(on_finish));
 
-  if (!scrollable_area_->ScrollAnimatorEnabled() ||
-      granularity == ScrollGranularity::kScrollByPrecisePixel) {
+  bool is_instant_scroll =
+#if defined(OS_MAC)
+      granularity == ScrollGranularity::kScrollByPixel ||
+#endif
+      !scrollable_area_->ScrollAnimatorEnabled() ||
+      granularity == ScrollGranularity::kScrollByPrecisePixel;
+
+  if (is_instant_scroll) {
     // Cancel scroll animation because asked to instant scroll.
     if (HasRunningAnimation())
       CancelAnimation();
@@ -387,6 +397,9 @@
   ScrollAnimatorCompositorCoordinator::CancelAnimation();
   if (on_finish_)
     std::move(on_finish_).Run();
+#if defined(OS_MAC)
+  have_scrolled_since_page_load_ = false;
+#endif
 }
 
 void ScrollAnimator::TakeOverCompositorAnimation() {
diff --git a/third_party/blink/renderer/core/scroll/scroll_animator.h b/third_party/blink/renderer/core/scroll/scroll_animator.h
index 4a6ec94..c2e9b79 100644
--- a/third_party/blink/renderer/core/scroll/scroll_animator.h
+++ b/third_party/blink/renderer/core/scroll/scroll_animator.h
@@ -34,6 +34,7 @@
 #include <memory>
 #include "base/time/default_tick_clock.h"
 
+#include "build/build_config.h"
 #include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation_client.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation_delegate.h"
@@ -130,6 +131,10 @@
 
   void Trace(Visitor*) const override;
 
+#if defined(OS_MAC)
+  bool HaveScrolledSincePageLoad() { return have_scrolled_since_page_load_; }
+#endif
+
  protected:
   // Returns whether or not the animation was sent to the compositor.
   virtual bool SendAnimationToCompositor();
@@ -157,6 +162,14 @@
   // on_finish_ is a callback to call on animation finished, cancelled, or
   // otherwise interrupted in any way.
   ScrollableArea::ScrollCallback on_finish_;
+
+  // TODO(crbug.com/1183387): investigate usage scenarios of this flag to verify
+  // if it is still useful.
+  // TODO(crbug.com/1122682): This is necessary for fade-in/out animations
+  // on Mac scrollbars. Remove this when MacScrollbarAnimatorImpl is removed.
+#if defined(OS_MAC)
+  bool have_scrolled_since_page_load_;
+#endif
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/scroll/scroll_animator_base.cc b/third_party/blink/renderer/core/scroll/scroll_animator_base.cc
index c40dd12..c59d4e1 100644
--- a/third_party/blink/renderer/core/scroll/scroll_animator_base.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_base.cc
@@ -60,8 +60,7 @@
   if (current_offset_ == new_pos)
     return ScrollResult(false, false, delta.Width(), delta.Height());
 
-  current_offset_ = new_pos;
-
+  SetCurrentOffset(new_pos);
   NotifyOffsetChanged();
 
   return ScrollResult(consumed_delta.Width(), consumed_delta.Height(),
@@ -71,7 +70,7 @@
 
 void ScrollAnimatorBase::ScrollToOffsetWithoutAnimation(
     const ScrollOffset& offset) {
-  current_offset_ = offset;
+  SetCurrentOffset(offset);
   NotifyOffsetChanged();
 }
 
diff --git a/third_party/blink/renderer/core/scroll/scroll_animator_base.h b/third_party/blink/renderer/core/scroll/scroll_animator_base.h
index 432a4e88..b8481d4 100644
--- a/third_party/blink/renderer/core/scroll/scroll_animator_base.h
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_base.h
@@ -41,7 +41,6 @@
 namespace blink {
 
 class ScrollableArea;
-class Scrollbar;
 
 // ScrollAnimatorBase is the common base class for all user scroll animators.
 // Every scrollable area has a lazily-created animator for user-input scrolls
@@ -58,8 +57,6 @@
   explicit ScrollAnimatorBase(ScrollableArea*);
   ~ScrollAnimatorBase() override;
 
-  virtual void Dispose() {}
-
   // A possibly animated scroll. The base class implementation always scrolls
   // immediately, never animates. If the scroll is animated and currently the
   // animator has an in-progress animation, the ScrollResult will always return
@@ -92,28 +89,6 @@
   void NotifyCompositorAnimationAborted(int group_id) override {}
   void MainThreadScrollingDidChange() override {}
 
-  virtual void ContentAreaWillPaint() const {}
-  virtual void MouseEnteredContentArea() const {}
-  virtual void MouseExitedContentArea() const {}
-  virtual void MouseMovedInContentArea() const {}
-  virtual void MouseEnteredScrollbar(Scrollbar&) const {}
-  virtual void MouseExitedScrollbar(Scrollbar&) const {}
-  virtual void ContentsResized() const {}
-  virtual void ContentAreaDidShow() const {}
-  virtual void ContentAreaDidHide() const {}
-
-  virtual void FinishCurrentScrollAnimations() {}
-
-  virtual void DidAddVerticalScrollbar(Scrollbar&) {}
-  virtual void WillRemoveVerticalScrollbar(Scrollbar&) {}
-  virtual void DidAddHorizontalScrollbar(Scrollbar&) {}
-  virtual void WillRemoveHorizontalScrollbar(Scrollbar&) {}
-
-  virtual void NotifyContentAreaScrolled(const ScrollOffset&,
-                                         mojom::blink::ScrollType) {}
-
-  virtual bool SetScrollbarsVisibleForTesting(bool) { return false; }
-
   void Trace(Visitor*) const override;
 
  protected:
diff --git a/third_party/blink/renderer/core/scroll/scroll_animator_mac.h b/third_party/blink/renderer/core/scroll/scroll_animator_mac.h
deleted file mode 100644
index 3138cef..0000000
--- a/third_party/blink/renderer/core/scroll/scroll_animator_mac.h
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2010, 2011 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_MAC_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_MAC_H_
-
-#include <memory>
-
-#include "base/mac/scoped_nsobject.h"
-#include "base/single_thread_task_runner.h"
-#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/geometry/float_point.h"
-#include "third_party/blink/renderer/platform/geometry/float_size.h"
-#include "third_party/blink/renderer/platform/geometry/int_rect.h"
-#include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
-#include "third_party/blink/renderer/platform/timer.h"
-
-@class BlinkScrollAnimationHelperDelegate;
-@class BlinkScrollbarPainterControllerDelegate;
-@class BlinkScrollbarPainterDelegate;
-
-typedef id ScrollbarPainterController;
-
-namespace blink {
-
-class Scrollbar;
-
-// ScrollAnimatorMac implements keyboard-triggered scroll offset animations,
-// scrollbar painting, and scrollbar opacity animations by delegating to native
-// Cocoa APIs.
-//
-// Scroll offset animations are also known as "smooth scrolling".  For the
-// non-Mac implementation of user input smooth scrolling, see ScrollAnimator.
-// For programmatic (CSSOM) smooth scrolls, see ProgrammaticScrollAnimator.
-//
-// Unlike ScrollAnimator, ScrollAnimatorMac only smooth-scrolls keyboard
-// scrolls, and not mouse wheel scrolls.  It also does not use compositor
-// animations or any of the standard Blink animation machinery.
-//
-// This divergence is mostly historical.  We could probably switch Mac to use
-// ScrollAnimator for smooth scrolls if we factored out the scrollbar-related
-// logic.  See crbug.com/574283 and crbug.com/682209.
-//
-// ScrollAnimatorMac's scroll offset animations are implemented by
-// NSScrollAnimationHelper which invokes a BlinkScrollAnimationHelperDelegate to
-// service an animation frame by performing an immediate scroll to the requested
-// offset (via NotifyOffsetChanged).
-//
-// The "scrollbar painter controller" is an NSScrollerImpPair object, which
-// calls back into Blink via BlinkScrollbarPainterControllerDelegate.
-//
-// The "scrollbar painter" is an NSScrollerImp object, which calls back into
-// Blink via BlinkScrollbarPainterDelegate.  The scrollbar painter is registered
-// with ScrollbarThemeMac, so that the ScrollbarTheme painting APIs can call
-// into it.
-//
-// The scrollbar painter initiates an overlay scrollbar fade-out animation by
-// calling animateKnobAlphaTo on the delegate.  This starts a timer inside the
-// BlinkScrollbarPartAnimationTimer.  Each tick evaluates a cubic bezier
-// function to obtain the current opacity, which is stored in the scrollbar
-// painter with setKnobAlpha.
-//
-// If the scroller is composited, the opacity value stored on the scrollbar
-// painter is subsequently read out through ScrollbarThemeMac::ThumbOpacity and
-// plumbed into PaintedScrollbarLayerImpl::thumb_opacity_.
-//
-// TODO: explain other types of animations (TrackAlpha, UIStateTransition,
-// ExpansionTransition), scrollbar paint timer, plumbing of scrollbar paint
-// invalidations.
-
-class CORE_EXPORT ScrollAnimatorMac : public ScrollAnimatorBase {
-  USING_PRE_FINALIZER(ScrollAnimatorMac, Dispose);
-
- public:
-  ScrollAnimatorMac(ScrollableArea*);
-  ~ScrollAnimatorMac() override;
-
-  void Dispose() override;
-
-  void ImmediateScrollToOffsetForScrollAnimation(
-      const ScrollOffset& new_offset);
-  bool HaveScrolledSincePageLoad() const {
-    return have_scrolled_since_page_load_;
-  }
-
-  void UpdateScrollerStyle();
-
-  bool ScrollbarPaintTimerIsActive() const;
-  void StartScrollbarPaintTimer();
-  void StopScrollbarPaintTimer();
-
-  void SendContentAreaScrolledSoon(const ScrollOffset& scroll_delta);
-
-  void Trace(Visitor* visitor) const override {
-    ScrollAnimatorBase::Trace(visitor);
-  }
-
- private:
-  base::scoped_nsobject<id> scroll_animation_helper_;
-  base::scoped_nsobject<BlinkScrollAnimationHelperDelegate>
-      scroll_animation_helper_delegate_;
-
-  base::scoped_nsobject<ScrollbarPainterController>
-      scrollbar_painter_controller_;
-  base::scoped_nsobject<BlinkScrollbarPainterControllerDelegate>
-      scrollbar_painter_controller_delegate_;
-  base::scoped_nsobject<BlinkScrollbarPainterDelegate>
-      horizontal_scrollbar_painter_delegate_;
-  base::scoped_nsobject<BlinkScrollbarPainterDelegate>
-      vertical_scrollbar_painter_delegate_;
-
-  void InitialScrollbarPaintTask();
-  TaskHandle initial_scrollbar_paint_task_handle_;
-
-  void SendContentAreaScrolledTask();
-  TaskHandle send_content_area_scrolled_task_handle_;
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-  ScrollOffset content_area_scrolled_timer_scroll_delta_;
-
-  ScrollResult UserScroll(ScrollGranularity,
-                          const ScrollOffset& delta,
-                          ScrollableArea::ScrollCallback on_finish) override;
-  void ScrollToOffsetWithoutAnimation(const ScrollOffset&) override;
-
-  void CancelAnimation() override;
-
-  void ContentAreaWillPaint() const override;
-  void MouseEnteredContentArea() const override;
-  void MouseExitedContentArea() const override;
-  void MouseMovedInContentArea() const override;
-  void MouseEnteredScrollbar(Scrollbar&) const override;
-  void MouseExitedScrollbar(Scrollbar&) const override;
-  void ContentsResized() const override;
-  void ContentAreaDidShow() const override;
-  void ContentAreaDidHide() const override;
-
-  void FinishCurrentScrollAnimations() override;
-
-  void DidAddVerticalScrollbar(Scrollbar&) override;
-  void WillRemoveVerticalScrollbar(Scrollbar&) override;
-  void DidAddHorizontalScrollbar(Scrollbar&) override;
-  void WillRemoveHorizontalScrollbar(Scrollbar&) override;
-
-  void NotifyContentAreaScrolled(const ScrollOffset& delta,
-                                 mojom::blink::ScrollType) override;
-
-  bool SetScrollbarsVisibleForTesting(bool) override;
-
-  ScrollOffset AdjustScrollOffsetIfNecessary(const ScrollOffset&) const;
-
-  void ImmediateScrollTo(const ScrollOffset&);
-
-  bool have_scrolled_since_page_load_;
-  bool needs_scroller_style_update_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_MAC_H_
diff --git a/third_party/blink/renderer/core/scroll/scroll_animator_test.cc b/third_party/blink/renderer/core/scroll/scroll_animator_test.cc
index 9d538ac..a967e70 100644
--- a/third_party/blink/renderer/core/scroll/scroll_animator_test.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_test.cc
@@ -29,6 +29,7 @@
 
 #include "base/single_thread_task_runner.h"
 #include "base/test/test_mock_time_task_runner.h"
+#include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -252,8 +253,14 @@
   ScrollAnimator* scroll_animator = MakeGarbageCollected<ScrollAnimator>(
       scrollable_area, task_runner->GetMockTickClock());
 
+#if defined(OS_MAC)
+  // Mac doesn't run animations for kScrollByPixel test case.
+  EXPECT_CALL(*scrollable_area, UpdateScrollOffset(_, _)).Times(8);
+  EXPECT_CALL(*scrollable_area, RegisterForAnimation()).Times(4);
+#else
   EXPECT_CALL(*scrollable_area, UpdateScrollOffset(_, _)).Times(9);
   EXPECT_CALL(*scrollable_area, RegisterForAnimation()).Times(6);
+#endif
   EXPECT_CALL(*scrollable_area, ScheduleAnimation())
       .Times(AtLeast(1))
       .WillRepeatedly(Return(true));
@@ -300,6 +307,13 @@
   scroll_animator->UserScroll(ScrollGranularity::kScrollByPixel,
                               FloatSize(100, 0),
                               ScrollableArea::ScrollCallback());
+// On Mac, kScrollByPixel also causes instant scroll.
+#if defined(OS_MAC)
+  EXPECT_FALSE(scroll_animator->HasAnimationThatRequiresService());
+  EXPECT_EQ(100, scroll_animator->CurrentOffset().Width());
+  EXPECT_NE(0, scroll_animator->CurrentOffset().Width());
+  EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
+#else
   EXPECT_TRUE(scroll_animator->HasAnimationThatRequiresService());
 
   task_runner->FastForwardBy(base::TimeDelta::FromMilliseconds(50));
@@ -318,6 +332,7 @@
   scroll_animator->UpdateCompositorAnimations();
   EXPECT_FALSE(scroll_animator->HasAnimationThatRequiresService());
   EXPECT_EQ(100, scroll_animator->CurrentOffset().Width());
+#endif
 
   Reset(*scroll_animator);
 
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.cc b/third_party/blink/renderer/core/scroll/scrollable_area.cc
index 7669bcb..c9914bbf 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.cc
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.cc
@@ -48,6 +48,7 @@
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
+#include "third_party/blink/renderer/core/scroll/mac_scrollbar_animator.h"
 #include "third_party/blink/renderer/core/scroll/programmatic_scroll_animator.h"
 #include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
 #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
@@ -110,6 +111,9 @@
       uses_composited_scrolling_(false),
       compositor_task_runner_(std::move(compositor_task_runner)) {
   DCHECK(compositor_task_runner_);
+#if defined(OS_MAC)
+  scrollbar_animator_ = MacScrollbarAnimator::Create(this);
+#endif
 }
 
 ScrollableArea::~ScrollableArea() = default;
@@ -123,16 +127,18 @@
 }
 
 void ScrollableArea::ClearScrollableArea() {
-#if defined(OS_MAC)
-  if (scroll_animator_)
-    scroll_animator_->Dispose();
-#endif
+  if (scrollbar_animator_)
+    scrollbar_animator_->Dispose();
   scroll_animator_.Clear();
   programmatic_scroll_animator_.Clear();
   if (fade_overlay_scrollbars_timer_)
     fade_overlay_scrollbars_timer_->Value().Stop();
 }
 
+MacScrollbarAnimator* ScrollableArea::GetMacScrollbarAnimator() const {
+  return scrollbar_animator_;
+}
+
 ScrollAnimatorBase& ScrollableArea::GetScrollAnimator() const {
   if (!scroll_animator_)
     scroll_animator_ =
@@ -412,9 +418,9 @@
   // TODO(skobes): Should we exit sooner when the offset has not changed?
   bool offset_changed = !delta.IsZero();
 
-  if (offset_changed) {
-    GetScrollAnimator().NotifyContentAreaScrolled(
-        GetScrollOffset() - old_offset, scroll_type);
+  if (GetMacScrollbarAnimator() && offset_changed &&
+      IsExplicitScrollType(scroll_type) && ScrollbarsCanBeActive()) {
+    GetMacScrollbarAnimator()->DidChangeUserVisibleScrollOffset(delta);
   }
 
   if (GetLayoutBox()) {
@@ -430,7 +436,7 @@
 
   if (offset_changed && GetLayoutBox() && GetLayoutBox()->GetFrameView()) {
     GetLayoutBox()->GetFrameView()->GetLayoutShiftTracker().NotifyScroll(
-        scroll_type, delta);
+        scroll_type);
   }
 
   GetScrollAnimator().SetCurrentOffset(offset);
@@ -470,28 +476,30 @@
 }
 
 void ScrollableArea::ContentAreaWillPaint() const {
-  if (ScrollAnimatorBase* scroll_animator = ExistingScrollAnimator())
-    scroll_animator->ContentAreaWillPaint();
+  if (GetMacScrollbarAnimator())
+    GetMacScrollbarAnimator()->ContentAreaWillPaint();
 }
 
 void ScrollableArea::MouseEnteredContentArea() const {
-  if (ScrollAnimatorBase* scroll_animator = ExistingScrollAnimator())
-    scroll_animator->MouseEnteredContentArea();
+  if (GetMacScrollbarAnimator())
+    GetMacScrollbarAnimator()->MouseEnteredContentArea();
 }
 
 void ScrollableArea::MouseExitedContentArea() const {
-  if (ScrollAnimatorBase* scroll_animator = ExistingScrollAnimator())
-    scroll_animator->MouseExitedContentArea();
+  if (GetMacScrollbarAnimator())
+    GetMacScrollbarAnimator()->MouseExitedContentArea();
 }
 
 void ScrollableArea::MouseMovedInContentArea() const {
-  if (ScrollAnimatorBase* scroll_animator = ExistingScrollAnimator())
-    scroll_animator->MouseMovedInContentArea();
+  if (GetMacScrollbarAnimator())
+    GetMacScrollbarAnimator()->MouseMovedInContentArea();
 }
 
 void ScrollableArea::MouseEnteredScrollbar(Scrollbar& scrollbar) {
   mouse_over_scrollbar_ = true;
-  GetScrollAnimator().MouseEnteredScrollbar(scrollbar);
+
+  if (GetMacScrollbarAnimator())
+    GetMacScrollbarAnimator()->MouseEnteredScrollbar(scrollbar);
   ShowNonMacOverlayScrollbars();
   if (fade_overlay_scrollbars_timer_)
     fade_overlay_scrollbars_timer_->Value().Stop();
@@ -499,7 +507,9 @@
 
 void ScrollableArea::MouseExitedScrollbar(Scrollbar& scrollbar) {
   mouse_over_scrollbar_ = false;
-  GetScrollAnimator().MouseExitedScrollbar(scrollbar);
+
+  if (GetMacScrollbarAnimator())
+    GetMacScrollbarAnimator()->MouseExitedScrollbar(scrollbar);
   if (HasOverlayScrollbars() && !scrollbars_hidden_if_overlay_) {
     // This will kick off the fade out timer.
     ShowNonMacOverlayScrollbars();
@@ -519,27 +529,14 @@
   ShowNonMacOverlayScrollbars();
 }
 
-void ScrollableArea::ContentAreaDidShow() const {
-  if (ScrollAnimatorBase* scroll_animator = ExistingScrollAnimator())
-    scroll_animator->ContentAreaDidShow();
-}
-
-void ScrollableArea::ContentAreaDidHide() const {
-  if (ScrollAnimatorBase* scroll_animator = ExistingScrollAnimator())
-    scroll_animator->ContentAreaDidHide();
-}
-
-void ScrollableArea::FinishCurrentScrollAnimations() const {
-  if (ScrollAnimatorBase* scroll_animator = ExistingScrollAnimator())
-    scroll_animator->FinishCurrentScrollAnimations();
-}
-
 void ScrollableArea::DidAddScrollbar(Scrollbar& scrollbar,
                                      ScrollbarOrientation orientation) {
-  if (orientation == kVerticalScrollbar)
-    GetScrollAnimator().DidAddVerticalScrollbar(scrollbar);
-  else
-    GetScrollAnimator().DidAddHorizontalScrollbar(scrollbar);
+  if (GetMacScrollbarAnimator()) {
+    if (orientation == kVerticalScrollbar)
+      GetMacScrollbarAnimator()->DidAddVerticalScrollbar(scrollbar);
+    else
+      GetMacScrollbarAnimator()->DidAddHorizontalScrollbar(scrollbar);
+  }
 
   // <rdar://problem/9797253> AppKit resets the scrollbar's style when you
   // attach a scrollbar
@@ -548,17 +545,17 @@
 
 void ScrollableArea::WillRemoveScrollbar(Scrollbar& scrollbar,
                                          ScrollbarOrientation orientation) {
-  if (ScrollAnimatorBase* scroll_animator = ExistingScrollAnimator()) {
+  if (GetMacScrollbarAnimator()) {
     if (orientation == kVerticalScrollbar)
-      scroll_animator->WillRemoveVerticalScrollbar(scrollbar);
+      GetMacScrollbarAnimator()->WillRemoveVerticalScrollbar(scrollbar);
     else
-      scroll_animator->WillRemoveHorizontalScrollbar(scrollbar);
+      GetMacScrollbarAnimator()->WillRemoveHorizontalScrollbar(scrollbar);
   }
 }
 
 void ScrollableArea::ContentsResized() {
-  if (ScrollAnimatorBase* scroll_animator = ExistingScrollAnimator())
-    scroll_animator->ContentsResized();
+  if (GetMacScrollbarAnimator())
+    GetMacScrollbarAnimator()->ContentsResized();
 }
 
 void ScrollableArea::InvalidateScrollTimeline() {
@@ -992,6 +989,7 @@
 
 void ScrollableArea::Trace(Visitor* visitor) const {
   visitor->Trace(scroll_animator_);
+  visitor->Trace(scrollbar_animator_);
   visitor->Trace(programmatic_scroll_animator_);
   visitor->Trace(fade_overlay_scrollbars_timer_);
 }
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.h b/third_party/blink/renderer/core/scroll/scrollable_area.h
index 3f42008..e310c632 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.h
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.h
@@ -65,6 +65,7 @@
 class PaintLayer;
 class ProgrammaticScrollAnimator;
 class ScrollAnchor;
+class MacScrollbarAnimator;
 class ScrollAnimatorBase;
 struct SerializedAnchor;
 class SmoothScrollSequencer;
@@ -155,8 +156,6 @@
   void MouseExitedScrollbar(Scrollbar&);
   void MouseCapturedScrollbar();
   void MouseReleasedScrollbar();
-  void ContentAreaDidShow() const;
-  void ContentAreaDidHide() const;
 
   virtual const cc::SnapContainerData* GetSnapContainerData() const {
     return nullptr;
@@ -207,8 +206,6 @@
     return base::nullopt;
   }
 
-  void FinishCurrentScrollAnimations() const;
-
   virtual void DidAddScrollbar(Scrollbar&, ScrollbarOrientation);
   virtual void WillRemoveScrollbar(Scrollbar&, ScrollbarOrientation);
 
@@ -230,6 +227,8 @@
         scrollbar_overlay_color_theme_);
   }
 
+  MacScrollbarAnimator* GetMacScrollbarAnimator() const;
+
   // This getter will create a ScrollAnimatorBase if it doesn't already exist.
   ScrollAnimatorBase& GetScrollAnimator() const;
 
@@ -371,7 +370,6 @@
   virtual IntPoint LastKnownMousePosition() const { return IntPoint(); }
 
   virtual bool ShouldSuspendScrollAnimations() const { return true; }
-  virtual void ScrollbarStyleChanged() {}
   virtual bool ScrollbarsCanBeActive() const = 0;
 
   virtual CompositorElementId GetScrollElementId() const = 0;
@@ -405,7 +403,7 @@
   // Overlay scrollbars can "fade-out" when inactive. This value should only be
   // updated if BlinkControlsOverlayVisibility is true in the
   // ScrollbarTheme. On Mac, where it is false, this can only be updated from
-  // the ScrollbarAnimatorMac painting code which will do so via
+  // the MacScrollbarAnimatorImpl painting code which will do so via
   // SetScrollbarsHiddenFromExternalAnimator.
   virtual bool ScrollbarsHiddenIfOverlay() const;
   void SetScrollbarsHiddenIfOverlay(bool);
@@ -631,6 +629,12 @@
           mojom::blink::ScrollBehavior::kSmooth,
       base::ScopedClosureRunner on_finish = base::ScopedClosureRunner());
 
+  // This animator is used to handle painting animations for MacOS scrollbars
+  // using AppKit-specific code (Cocoa APIs). It requires input from
+  // ScrollableArea about changes on scrollbars. For other platforms, painting
+  // is done by blink, and this member will be a nullptr.
+  mutable Member<MacScrollbarAnimator> scrollbar_animator_;
+
   mutable Member<ScrollAnimatorBase> scroll_animator_;
   mutable Member<ProgrammaticScrollAnimator> programmatic_scroll_animator_;
 
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm b/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm
index bcc6e202..5cb18b0b 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm
@@ -34,7 +34,6 @@
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_theme_engine.h"
 #include "third_party/blink/renderer/core/scroll/ns_scroller_imp_details.h"
-#include "third_party/blink/renderer/core/scroll/scroll_animator_mac.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
@@ -181,7 +180,7 @@
     const Scrollbar& scrollbar,
     float old_position,
     float new_position) const {
-  // ScrollAnimatorMac will invalidate scrollbar parts if necessary.
+  // MacScrollbarAnimatorImpl will invalidate scrollbar parts if necessary.
   return kNoPart;
 }
 
diff --git a/third_party/blink/renderer/core/testing/data/apphistory/onnavigate-preventDefault.html b/third_party/blink/renderer/core/testing/data/apphistory/onnavigate-preventDefault.html
new file mode 100644
index 0000000..a4dc4b1
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/apphistory/onnavigate-preventDefault.html
@@ -0,0 +1,3 @@
+<script>
+appHistory.onnavigate = e => e.preventDefault();
+</script>
diff --git a/third_party/blink/renderer/core/testing/internal_settings.cc b/third_party/blink/renderer/core/testing/internal_settings.cc
index bb4fc83..e27c9852 100644
--- a/third_party/blink/renderer/core/testing/internal_settings.cc
+++ b/third_party/blink/renderer/core/testing/internal_settings.cc
@@ -29,7 +29,6 @@
 #include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
-#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
 #include "third_party/blink/renderer/platform/text/locale_to_script_mapping.h"
 
@@ -61,9 +60,7 @@
 using mojom::blink::PointerType;
 
 InternalSettings::Backup::Backup(Settings* settings)
-    : original_csp_(RuntimeEnabledFeatures::
-                        ExperimentalContentSecurityPolicyFeaturesEnabled()),
-      original_editing_behavior_(settings->GetEditingBehaviorType()),
+    : original_editing_behavior_(settings->GetEditingBehaviorType()),
       original_text_autosizing_enabled_(settings->GetTextAutosizingEnabled()),
       original_text_autosizing_window_size_override_(
           settings->GetTextAutosizingWindowSizeOverride()),
@@ -73,17 +70,11 @@
       original_display_mode_override_(settings->GetDisplayModeOverride()),
       original_mock_gesture_tap_highlights_enabled_(
           settings->GetMockGestureTapHighlightsEnabled()),
-      lang_attribute_aware_form_control_ui_enabled_(
-          RuntimeEnabledFeatures::LangAttributeAwareFormControlUIEnabled()),
       images_enabled_(settings->GetImagesEnabled()),
       default_video_poster_url_(settings->GetDefaultVideoPosterURL()),
-      original_image_animation_policy_(settings->GetImageAnimationPolicy()),
-      original_scroll_top_left_interop_enabled_(
-          RuntimeEnabledFeatures::ScrollTopLeftInteropEnabled()) {}
+      original_image_animation_policy_(settings->GetImageAnimationPolicy()) {}
 
 void InternalSettings::Backup::RestoreTo(Settings* settings) {
-  RuntimeEnabledFeatures::SetExperimentalContentSecurityPolicyFeaturesEnabled(
-      original_csp_);
   settings->SetEditingBehaviorType(original_editing_behavior_);
   settings->SetTextAutosizingEnabled(original_text_autosizing_enabled_);
   settings->SetTextAutosizingWindowSizeOverride(
@@ -94,14 +85,10 @@
   settings->SetDisplayModeOverride(original_display_mode_override_);
   settings->SetMockGestureTapHighlightsEnabled(
       original_mock_gesture_tap_highlights_enabled_);
-  RuntimeEnabledFeatures::SetLangAttributeAwareFormControlUIEnabled(
-      lang_attribute_aware_form_control_ui_enabled_);
   settings->SetImagesEnabled(images_enabled_);
   settings->SetDefaultVideoPosterURL(default_video_poster_url_);
   settings->GetGenericFontFamilySettings().Reset();
   settings->SetImageAnimationPolicy(original_image_animation_policy_);
-  RuntimeEnabledFeatures::SetScrollTopLeftInteropEnabled(
-      original_scroll_top_left_interop_enabled_);
 }
 
 InternalSettings* InternalSettings::From(Page& page) {
@@ -148,12 +135,6 @@
   GetSettings()->SetMockGestureTapHighlightsEnabled(enabled);
 }
 
-void InternalSettings::setExperimentalContentSecurityPolicyFeaturesEnabled(
-    bool enabled) {
-  RuntimeEnabledFeatures::SetExperimentalContentSecurityPolicyFeaturesEnabled(
-      enabled);
-}
-
 void InternalSettings::setViewportEnabled(bool enabled,
                                           ExceptionState& exception_state) {
   InternalSettingsGuardForSettings();
@@ -339,10 +320,6 @@
   }
 }
 
-void InternalSettings::setLangAttributeAwareFormControlUIEnabled(bool enabled) {
-  RuntimeEnabledFeatures::SetLangAttributeAwareFormControlUIEnabled(enabled);
-}
-
 void InternalSettings::setImagesEnabled(bool enabled,
                                         ExceptionState& exception_state) {
   InternalSettingsGuardForSettings();
@@ -506,10 +483,6 @@
   }
 }
 
-void InternalSettings::setScrollTopLeftInteropEnabled(bool enabled) {
-  RuntimeEnabledFeatures::SetScrollTopLeftInteropEnabled(enabled);
-}
-
 void InternalSettings::SetDnsPrefetchLogging(bool enabled,
                                              ExceptionState& exception_state) {
   InternalSettingsGuardForSettings();
diff --git a/third_party/blink/renderer/core/testing/internal_settings.h b/third_party/blink/renderer/core/testing/internal_settings.h
index f6725bff..38b8cb2 100644
--- a/third_party/blink/renderer/core/testing/internal_settings.h
+++ b/third_party/blink/renderer/core/testing/internal_settings.h
@@ -54,7 +54,6 @@
     explicit Backup(Settings*);
     void RestoreTo(Settings*);
 
-    bool original_csp_;
     bool original_overlay_scrollbars_enabled_;
     mojom::EditingBehavior original_editing_behavior_;
     bool original_text_autosizing_enabled_;
@@ -63,11 +62,9 @@
     String original_media_type_override_;
     blink::mojom::DisplayMode original_display_mode_override_;
     bool original_mock_gesture_tap_highlights_enabled_;
-    bool lang_attribute_aware_form_control_ui_enabled_;
     bool images_enabled_;
     String default_video_poster_url_;
     mojom::blink::ImageAnimationPolicy original_image_animation_policy_;
-    bool original_scroll_top_left_interop_enabled_;
   };
 
   static InternalSettings* From(Page&);
@@ -98,7 +95,6 @@
   void setPictographFontFamily(const AtomicString& family,
                                const String& script,
                                ExceptionState&);
-
   void setDefaultVideoPosterURL(const String& url, ExceptionState&);
   void setEditingBehavior(const String&, ExceptionState&);
   void setImagesEnabled(bool, ExceptionState&);
@@ -120,17 +116,7 @@
   void setPresentationReceiver(bool, ExceptionState&);
   void setAutoplayPolicy(const String&, ExceptionState&);
   void setUniversalAccessFromFileURLs(bool, ExceptionState&);
-
-  // FIXME: The following are RuntimeEnabledFeatures and likely
-  // cannot be changed after process start. These setters should
-  // be removed or moved onto internals.runtimeFlags:
-  void setLangAttributeAwareFormControlUIEnabled(bool);
-  void setExperimentalContentSecurityPolicyFeaturesEnabled(bool);
   void setImageAnimationPolicy(const String&, ExceptionState&);
-  void setScrollTopLeftInteropEnabled(bool);
-
-  void Trace(Visitor*) const override;
-
   void setAvailablePointerTypes(const String&, ExceptionState&);
   void setPrimaryPointerType(const String&, ExceptionState&);
   void setAvailableHoverTypes(const String&, ExceptionState&);
@@ -138,6 +124,8 @@
   void SetDnsPrefetchLogging(bool, ExceptionState&);
   void SetPreloadLogging(bool, ExceptionState&);
 
+  void Trace(Visitor*) const override;
+
  private:
   Settings* GetSettings() const;
   Page* GetPage() const { return GetSupplementable(); }
diff --git a/third_party/blink/renderer/core/testing/internal_settings.idl b/third_party/blink/renderer/core/testing/internal_settings.idl
index c49c136..664501b2 100644
--- a/third_party/blink/renderer/core/testing/internal_settings.idl
+++ b/third_party/blink/renderer/core/testing/internal_settings.idl
@@ -55,11 +55,4 @@
     [RaisesException] void setPresentationReceiver(boolean enabled);
     [RaisesException] void setAutoplayPolicy(DOMString policy);
     [RaisesException] void setUniversalAccessFromFileURLs(boolean enabled);
-
-    // FIXME: The following are RuntimeEnabledFeatures and likely
-    // cannot be changed after process start.  These setters should
-    // be removed or moved onto internals.runtimeFlags:
-    void setLangAttributeAwareFormControlUIEnabled(boolean enabled);
-    void setExperimentalContentSecurityPolicyFeaturesEnabled(boolean enabled);
-    void setScrollTopLeftInteropEnabled(boolean enabled);
 };
diff --git a/third_party/blink/renderer/core/testing/internals.cc b/third_party/blink/renderer/core/testing/internals.cc
index bb13bae..300103fd 100644
--- a/third_party/blink/renderer/core/testing/internals.cc
+++ b/third_party/blink/renderer/core/testing/internals.cc
@@ -140,6 +140,7 @@
 #include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/core/script/import_map.h"
 #include "third_party/blink/renderer/core/script/modulator.h"
+#include "third_party/blink/renderer/core/scroll/mac_scrollbar_animator.h"
 #include "third_party/blink/renderer/core/scroll/programmatic_scroll_animator.h"
 #include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
 #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
@@ -3663,8 +3664,12 @@
                                                        bool visible) {
   if (ScrollableArea* scrollable_area = ScrollableAreaForNode(node)) {
     scrollable_area->SetScrollbarsHiddenForTesting(!visible);
-    scrollable_area->GetScrollAnimator().SetScrollbarsVisibleForTesting(
-        visible);
+
+    if (MacScrollbarAnimator* scrollbar_animator =
+            scrollable_area->GetMacScrollbarAnimator()) {
+      scrollbar_animator->SetScrollbarsVisibleForTesting(visible);
+    }
+
     return scrollable_area->GetPageScrollbarTheme().UsesOverlayScrollbars();
   }
   return false;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
index b65ab95..ffdde2d3 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
@@ -191,11 +191,8 @@
     return DetermineTableCellRole();
 
   if (IsImageOrAltText(layout_object_, node)) {
-    if (node && node->IsLink())
-      return ax::mojom::blink::Role::kImageMap;
     if (IsA<HTMLInputElement>(node))
       return ButtonRoleType();
-
     return ax::mojom::blink::Role::kImage;
   }
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_media_element.cc b/third_party/blink/renderer/modules/accessibility/ax_media_element.cc
index e12b63f..e2268f69 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_media_element.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_media_element.cc
@@ -32,6 +32,9 @@
     ax::mojom::NameFrom& name_from,
     AXRelatedObjectVector* related_objects,
     NameSources* name_sources) const {
+  if (IsDetached())
+    return String();
+
   if (IsUnplayable()) {
     HTMLMediaElement* element =
         static_cast<HTMLMediaElement*>(layout_object_->GetNode());
@@ -48,7 +51,7 @@
 
 bool AccessibilityMediaElement::ComputeAccessibilityIsIgnored(
     IgnoredReasons* ignored_reasons) const {
-  return !HasControls() && HasEmptySource();
+  return false;
 }
 
 AXRestriction AccessibilityMediaElement::Restriction() const {
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index 840f968f..79bc333 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -801,25 +801,12 @@
   if (!GetNode())
     return RoleFromLayoutObject(ax::mojom::blink::Role::kUnknown);
 
-  if (GetNode()->IsLink()) {
-    if (IsA<HTMLImageElement>(GetNode())) {
-      // If the image will have area children, it is a map, otherwise an image.
-      // Unlike most roles, this may be recomputed again in the lifetime of
-      // |this| object, as children are gained or removed.
-      HTMLMapElement* map_element = GetMapForImage(GetLayoutObject());
-      // Make sure this is the primary image for this <map>. For more details on
-      // multiple images referring to the same map, see AddImageMapChildren().
-      if (map_element && map_element->ImageElement() == GetElement())
-        return ax::mojom::blink::Role::kImageMap;
-      return ax::mojom::blink::Role::kImage;
-    }
-    if (IsA<HTMLImageElement>(GetNode())) {
-      return children_.size() ? ax::mojom::blink::Role::kImageMap
-                              : ax::mojom::blink::Role::kImage;
-    } else {  // <a href> or <svg:a xlink:href>
-      // |HTMLAnchorElement| sets isLink only when it has kHrefAttr.
-      return ax::mojom::blink::Role::kLink;
-    }
+  if (IsA<HTMLImageElement>(GetNode()))
+    return ax::mojom::blink::Role::kImage;
+
+  if (GetNode()->IsLink()) {  // <a href> or <svg:a xlink:href>
+    // |HTMLAnchorElement| sets isLink only when it has kHrefAttr.
+    return ax::mojom::blink::Role::kLink;
   }
 
   if (IsA<HTMLPortalElement>(*GetNode())) {
@@ -3621,17 +3608,8 @@
         ax_primary_image->ChildCountIncludingIgnored() == 0 &&
         Traversal<HTMLAreaElement>::FirstWithin(*map)) {
       // The primary image still needs to add the area children, and there's at
-      // least one to add. Its role also needs to change to kImageMap.
-      // The children change will force the role change as well.
+      // least one to add.
       AXObjectCache().ChildrenChanged(primary_image_element);
-      // The current image may change role from an image map to an image.
-      // Unlike many role changes, this can be done on the same object.
-      // There's no need to fire a role changed event or MarkDirty because the
-      // only time the role changes is when we're updating children anyway.
-      if (role_ == ax::mojom::blink::Role::kImageMap) {
-        ax_primary_image->ClearChildren();  // Clear the children now.
-        ax_primary_image->UpdateRoleForImage();
-      }
     }
     return;
   }
@@ -3647,11 +3625,7 @@
         DCHECK(ax_previous_parent->GetNode());
         AXObjectCache().ChildrenChangedWithCleanLayout(
             ax_previous_parent->GetNode(), ax_previous_parent);
-        if (ax_previous_parent->RoleValue() ==
-            ax::mojom::blink::Role::kImageMap) {
           ax_previous_parent->ClearChildren();
-          ax_previous_parent->UpdateRoleForImage();
-        }
       }
     }
 
@@ -3661,14 +3635,6 @@
       // Add an <area> element for this child if it has a link and is visible.
       AddChildAndCheckIncluded(AXObjectCache().GetOrCreate(&area, this));
     }
-
-    // The current image may change role from an image to an image map.
-    // Unlike many role changes, this can be done on the same object.
-    if (role_ == ax::mojom::blink::Role::kImage) {
-      // There's no need to fire a role changed event or MarkDirty because the
-      // only time the role changes is when we're updating children anyway.
-      UpdateRoleForImage();
-    }
   }
 }
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index f560a5f1..9dd6ce9 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -497,7 +497,6 @@
     {ax::mojom::blink::Role::kIframePresentational, "IframePresentational"},
     {ax::mojom::blink::Role::kIframe, "Iframe"},
     {ax::mojom::blink::Role::kIgnored, "Ignored"},
-    {ax::mojom::blink::Role::kImageMap, "ImageMap"},
     {ax::mojom::blink::Role::kImage, "Image"},
     {ax::mojom::blink::Role::kImeCandidate, "ImeCandidate"},
     {ax::mojom::blink::Role::kInlineTextBox, "InlineTextBox"},
@@ -946,10 +945,10 @@
   if (!GetNode() || !GetLayoutObject())
     return;
 
-  // Don't check the computed parent if the cached parent is an image map:
+  // Don't check the computed parent if the cached parent is an image:
   // <area> children's location in the DOM and HTML hierarchy does not match.
   // TODO(aleventhal) Try to remove this rule, it may be unnecessary now.
-  if (parent_->RoleValue() == ax::mojom::blink::Role::kImageMap)
+  if (parent_->RoleValue() == ax::mojom::blink::Role::kImage)
     return;
 
   // TODO(aleventhal) Different in test fast/css/first-letter-removed-added.html
@@ -1451,8 +1450,13 @@
 
 void AXObject::SerializeHTMLTagAndClass(ui::AXNodeData* node_data) {
   Element* element = GetElement();
-  if (!element)
+  if (!element) {
+    if (ui::IsPlatformDocument(RoleValue())) {
+      TruncateAndAddStringAttribute(
+          node_data, ax::mojom::blink::StringAttribute::kHtmlTag, "#document");
+    }
     return;
+  }
 
   TruncateAndAddStringAttribute(node_data,
                                 ax::mojom::blink::StringAttribute::kHtmlTag,
@@ -3555,14 +3559,6 @@
   return ax::mojom::blink::Role::kUnknown;
 }
 
-void AXObject::UpdateRoleForImage() {
-  // There's no need to fire a role changed event or MarkDirty because the
-  // only time the role changes is when we're updating children anyway.
-  // TODO(accessibility) Use one Blink role and move this to the browser.
-  role_ = children_.size() ? ax::mojom::blink::Role::kImageMap
-                           : ax::mojom::blink::Role::kImage;
-}
-
 bool AXObject::IsEditableRoot() const {
   UpdateCachedAttributeValuesIfNeeded();
   return cached_is_editable_root_;
@@ -4066,10 +4062,10 @@
   if (!NeedsToUpdateChildren())
     return;
 
-    // Ensure children already cleared.
 #if DCHECK_IS_ON()
+  // Ensure there are no unexpected, preexisting children, before we add more.
   if (IsMenuList()) {
-    // AXMenuList is special and keeps its popup child.
+    // AXMenuList is special and keeps its popup child, even when cleared.
     DCHECK_LE(children_.size(), 1U);
   } else {
     // Ensure children have been correctly cleared.
@@ -5216,7 +5212,6 @@
     case ax::mojom::blink::Role::kFooterAsNonLandmark:
     case ax::mojom::blink::Role::kGenericContainer:
     case ax::mojom::blink::Role::kHeaderAsNonLandmark:
-    case ax::mojom::blink::Role::kImageMap:
     case ax::mojom::blink::Role::kInlineTextBox:
     case ax::mojom::blink::Role::kLabelText:
     case ax::mojom::blink::Role::kLayoutTable:
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.h b/third_party/blink/renderer/modules/accessibility/ax_object.h
index 705a5f6..a1814cc 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.h
@@ -779,7 +779,6 @@
 
   // ARIA attributes.
   virtual ax::mojom::blink::Role DetermineAccessibilityRole() = 0;
-  void UpdateRoleForImage();  // Set role to image (leaf) or map (has children).
   ax::mojom::blink::Role DetermineAriaRoleAttribute() const;
   virtual ax::mojom::blink::Role AriaRoleAttribute() const;
   virtual bool HasAriaAttribute() const { return false; }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index 1a6cd191..eb17965 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -2279,21 +2279,21 @@
 
   DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node));
 
-  // Invalidate the current object and make the parent reconsider its children.
+  // Remove the current object and make the parent reconsider its children.
   if (AXObject* obj = GetOrCreate(node)) {
-    // If role changes on a table, invalidate the entire table subtree as many
-    // objects may suddenly need to change, because presentation is inherited
-    // from the table to rows and cells.
+    // When the role of `obj` is changed, its AXObject needs to be destroyed and
+    // a new one needs to be created in its place. We destroy the current
+    // AXObject in this method and call ChildrenChangeWithCleanLayout() on the
+    // parent so that future updates to its children will create the alert.
+    ChildrenChangedWithCleanLayout(nullptr, obj->CachedParentObject());
     LayoutObject* layout_object = node->GetLayoutObject();
     if (layout_object && layout_object->IsTable()) {
-      AXObject* parent = obj->ParentObject();
+      // If role changes on a table, invalidate the entire table subtree as many
+      // objects may suddenly need to change, because presentation is inherited
+      // from the table to rows and cells.
       RemoveAXObjectsInLayoutSubtree(obj);
-      // Parent object changed children, as the previous AXObject for this node
-      // was destroyed and a different one was created in its place.
-      ChildrenChangedWithCleanLayout(nullptr, parent);
     } else {
-      // Will both refresh the object and call ChildrenChanged() on the parent.
-      Invalidate(obj->AXObjectID());
+      Remove(node);
     }
   }
 }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc b/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
index d88159a..96801d0 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
@@ -125,10 +125,10 @@
   if (owner->IsNativeTextControl() || owner->HasContentEditableAttributeSet())
     return false;
 
-  // Image maps can only use <img usemap> to "own" <area> children.
+  // Images can only use <img usemap> to "own" <area> children.
   // This requires special parenting logic, and aria-owns is prevented here in
   // order to keep things from getting too complex.
-  if (owner->RoleValue() == ax::mojom::blink::Role::kImageMap)
+  if (owner->RoleValue() == ax::mojom::blink::Role::kImage)
     return false;
 
   // Similarly, do not allow <area> to own another object.
diff --git a/third_party/blink/renderer/modules/webaudio/analyser_node.cc b/third_party/blink/renderer/modules/webaudio/analyser_node.cc
index 53528e0..9b6d691 100644
--- a/third_party/blink/renderer/modules/webaudio/analyser_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/analyser_node.cc
@@ -34,7 +34,9 @@
 namespace blink {
 
 AnalyserHandler::AnalyserHandler(AudioNode& node, float sample_rate)
-    : AudioBasicInspectorHandler(kNodeTypeAnalyser, node, sample_rate) {
+    : AudioBasicInspectorHandler(kNodeTypeAnalyser, node, sample_rate),
+      analyser_(std::make_unique<RealtimeAnalyser>(
+          node.context()->GetDeferredTaskHandler().RenderQuantumFrames())) {
   channel_count_ = 2;
   AddOutput(1);
 
@@ -63,7 +65,7 @@
   // Give the analyser the audio which is passing through this
   // AudioNode.  This must always be done so that the state of the
   // Analyser reflects the current input.
-  analyser_.WriteInput(input_bus.get(), frames_to_process);
+  analyser_->WriteInput(input_bus.get(), frames_to_process);
 
   if (!Input(0).IsConnected()) {
     // No inputs, so clear the output, and propagate the silence hint.
@@ -81,7 +83,7 @@
 
 void AnalyserHandler::SetFftSize(unsigned size,
                                  ExceptionState& exception_state) {
-  if (!analyser_.SetFftSize(size)) {
+  if (!analyser_->SetFftSize(size)) {
     exception_state.ThrowDOMException(
         DOMExceptionCode::kIndexSizeError,
         (size < RealtimeAnalyser::kMinFFTSize ||
@@ -99,7 +101,7 @@
 void AnalyserHandler::SetMinDecibels(double k,
                                      ExceptionState& exception_state) {
   if (k < MaxDecibels()) {
-    analyser_.SetMinDecibels(k);
+    analyser_->SetMinDecibels(k);
   } else {
     exception_state.ThrowDOMException(
         DOMExceptionCode::kIndexSizeError,
@@ -111,7 +113,7 @@
 void AnalyserHandler::SetMaxDecibels(double k,
                                      ExceptionState& exception_state) {
   if (k > MinDecibels()) {
-    analyser_.SetMaxDecibels(k);
+    analyser_->SetMaxDecibels(k);
   } else {
     exception_state.ThrowDOMException(
         DOMExceptionCode::kIndexSizeError,
@@ -131,15 +133,15 @@
             String::Number(min_decibels) + ").");
     return;
   }
-  analyser_.SetMinDecibels(min_decibels);
-  analyser_.SetMaxDecibels(max_decibels);
+  analyser_->SetMinDecibels(min_decibels);
+  analyser_->SetMaxDecibels(max_decibels);
 }
 
 void AnalyserHandler::SetSmoothingTimeConstant(
     double k,
     ExceptionState& exception_state) {
   if (k >= 0 && k <= 1) {
-    analyser_.SetSmoothingTimeConstant(k);
+    analyser_->SetSmoothingTimeConstant(k);
   } else {
     exception_state.ThrowDOMException(
         DOMExceptionCode::kIndexSizeError,
diff --git a/third_party/blink/renderer/modules/webaudio/analyser_node.h b/third_party/blink/renderer/modules/webaudio/analyser_node.h
index b859ca7..7f8e999 100644
--- a/third_party/blink/renderer/modules/webaudio/analyser_node.h
+++ b/third_party/blink/renderer/modules/webaudio/analyser_node.h
@@ -45,35 +45,35 @@
   // AudioHandler
   void Process(uint32_t frames_to_process) override;
 
-  unsigned FftSize() const { return analyser_.FftSize(); }
+  unsigned FftSize() const { return analyser_->FftSize(); }
   void SetFftSize(unsigned size, ExceptionState&);
 
-  unsigned FrequencyBinCount() const { return analyser_.FrequencyBinCount(); }
+  unsigned FrequencyBinCount() const { return analyser_->FrequencyBinCount(); }
 
   void SetMinDecibels(double k, ExceptionState&);
-  double MinDecibels() const { return analyser_.MinDecibels(); }
+  double MinDecibels() const { return analyser_->MinDecibels(); }
 
   void SetMaxDecibels(double k, ExceptionState&);
-  double MaxDecibels() const { return analyser_.MaxDecibels(); }
+  double MaxDecibels() const { return analyser_->MaxDecibels(); }
 
   void SetMinMaxDecibels(double min, double max, ExceptionState&);
 
   void SetSmoothingTimeConstant(double k, ExceptionState&);
   double SmoothingTimeConstant() const {
-    return analyser_.SmoothingTimeConstant();
+    return analyser_->SmoothingTimeConstant();
   }
 
   void GetFloatFrequencyData(DOMFloat32Array* array, double current_time) {
-    analyser_.GetFloatFrequencyData(array, current_time);
+    analyser_->GetFloatFrequencyData(array, current_time);
   }
   void GetByteFrequencyData(DOMUint8Array* array, double current_time) {
-    analyser_.GetByteFrequencyData(array, current_time);
+    analyser_->GetByteFrequencyData(array, current_time);
   }
   void GetFloatTimeDomainData(DOMFloat32Array* array) {
-    analyser_.GetFloatTimeDomainData(array);
+    analyser_->GetFloatTimeDomainData(array);
   }
   void GetByteTimeDomainData(DOMUint8Array* array) {
-    analyser_.GetByteTimeDomainData(array);
+    analyser_->GetByteTimeDomainData(array);
   }
 
   // AnalyserNode needs special handling when updating the pull status
@@ -94,7 +94,7 @@
     return false;
   }
 
-  RealtimeAnalyser analyser_;
+  std::unique_ptr<RealtimeAnalyser> analyser_;
 };
 
 class AnalyserNode final : public AudioBasicInspectorNode {
diff --git a/third_party/blink/renderer/modules/webaudio/realtime_analyser.cc b/third_party/blink/renderer/modules/webaudio/realtime_analyser.cc
index b11a828..059a80ed 100644
--- a/third_party/blink/renderer/modules/webaudio/realtime_analyser.cc
+++ b/third_party/blink/renderer/modules/webaudio/realtime_analyser.cc
@@ -46,9 +46,9 @@
 const unsigned RealtimeAnalyser::kInputBufferSize =
     RealtimeAnalyser::kMaxFFTSize * 2;
 
-RealtimeAnalyser::RealtimeAnalyser()
+RealtimeAnalyser::RealtimeAnalyser(unsigned render_quantum_frames)
     : input_buffer_(kInputBufferSize),
-      down_mix_bus_(AudioBus::Create(1, audio_utilities::kRenderQuantumFrames)),
+      down_mix_bus_(AudioBus::Create(1, render_quantum_frames)),
       fft_size_(kDefaultFFTSize),
       magnitude_buffer_(kDefaultFFTSize / 2),
       smoothing_time_constant_(kDefaultSmoothingTimeConstant),
diff --git a/third_party/blink/renderer/modules/webaudio/realtime_analyser.h b/third_party/blink/renderer/modules/webaudio/realtime_analyser.h
index 6a36b17a..47c325d 100644
--- a/third_party/blink/renderer/modules/webaudio/realtime_analyser.h
+++ b/third_party/blink/renderer/modules/webaudio/realtime_analyser.h
@@ -39,10 +39,8 @@
 class AudioBus;
 
 class RealtimeAnalyser final {
-  DISALLOW_NEW();
-
  public:
-  RealtimeAnalyser();
+  explicit RealtimeAnalyser(unsigned render_quantum_frames);
 
   uint32_t FftSize() const { return fft_size_; }
   bool SetFftSize(uint32_t);
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_host.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_host.cc
index d636062..fcb3fee 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_host.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_host.cc
@@ -44,4 +44,8 @@
   RestoreCanvasMatrixClipStack(canvas);
 }
 
+void CanvasResourceHost::SetFilterQuality(SkFilterQuality filter_quality) {
+  filter_quality_ = filter_quality;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_host.h b/third_party/blink/renderer/platform/graphics/canvas_resource_host.h
index f918d35..1d94d36 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_host.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_host.h
@@ -30,7 +30,8 @@
   virtual CanvasResourceProvider* GetOrCreateCanvasResourceProviderImpl(
       RasterModeHint hint) = 0;
 
-  virtual SkFilterQuality FilterQuality() const = 0;
+  virtual void SetFilterQuality(SkFilterQuality filter_quality);
+  SkFilterQuality FilterQuality() const { return filter_quality_; }
   virtual bool LowLatencyEnabled() const { return false; }
 
   CanvasResourceProvider* ResourceProvider() const;
@@ -46,6 +47,7 @@
   void InitializeForRecording(cc::PaintCanvas* canvas);
 
   std::unique_ptr<CanvasResourceProvider> resource_provider_;
+  SkFilterQuality filter_quality_ = kLow_SkFilterQuality;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/test/fake_canvas_resource_host.h b/third_party/blink/renderer/platform/graphics/test/fake_canvas_resource_host.h
index fda6222..b56b4205 100644
--- a/third_party/blink/renderer/platform/graphics/test/fake_canvas_resource_host.h
+++ b/third_party/blink/renderer/platform/graphics/test/fake_canvas_resource_host.h
@@ -63,10 +63,6 @@
     return ResourceProvider();
   }
 
-  SkFilterQuality FilterQuality() const override {
-    return kLow_SkFilterQuality;
-  }
-
  private:
   IntSize size_;
 };
diff --git a/third_party/blink/renderer/platform/heap/v8_wrapper/heap_test_utilities.h b/third_party/blink/renderer/platform/heap/v8_wrapper/heap_test_utilities.h
index cce9cb8e..1e69cb7 100644
--- a/third_party/blink/renderer/platform/heap/v8_wrapper/heap_test_utilities.h
+++ b/third_party/blink/renderer/platform/heap/v8_wrapper/heap_test_utilities.h
@@ -37,12 +37,14 @@
  public:
   // Performs a precise garbage collection with eager sweeping.
   static void PreciselyCollectGarbage() {
-    // TODO(1056170): Implement.
+    ThreadState::Current()->CollectAllGarbageForTesting(
+        BlinkGC::kNoHeapPointersOnStack);
   }
 
   // Performs a conservative garbage collection with eager sweeping.
   static void ConservativelyCollectGarbage() {
-    // TODO(1056170): Implement.
+    ThreadState::Current()->CollectAllGarbageForTesting(
+        BlinkGC::kHeapPointersOnStack);
   }
 
  protected:
diff --git a/third_party/blink/renderer/platform/loader/cors/cors_error_string.cc b/third_party/blink/renderer/platform/loader/cors/cors_error_string.cc
index 89c4c761..3b989c9e 100644
--- a/third_party/blink/renderer/platform/loader/cors/cors_error_string.cc
+++ b/third_party/blink/renderer/platform/loader/cors/cors_error_string.cc
@@ -111,7 +111,7 @@
       break;
     case CorsError::kInsecurePrivateNetwork:
       Append(builder, {"The request client is not a secure context and the "
-                       "resource is in more-private adress space `",
+                       "resource is in more-private address space `",
                        ShortAddressSpace(status.resource_address_space), "`."});
       break;
     case CorsError::kWildcardOriginNotAllowed:
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 39682bb..f794fae4 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -915,6 +915,7 @@
     {
       name: "FetchUploadStreaming",
       origin_trial_feature_name: "FetchUploadStreaming",
+      origin_trial_allows_third_party: true,
       status: "experimental",
     },
     {
@@ -1103,6 +1104,7 @@
     },
     {
       name: "LangAttributeAwareFormControlUI",
+      settable_from_internals: true,
     },
     {
       name: "LangClientHintHeader",
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index 4135a50..0e52d848 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -508,14 +508,15 @@
             'ax::mojom::Restriction',
 
             # Accessibility helper functions - mostly used in Blink for
-            # serialization.
+            # serialization. Please keep alphabetized.
             'ui::CanHaveInlineTextBoxChildren',
+            'ui::IsCellOrTableHeader',
+            'ui::IsContainerWithSelectableChildren',
             'ui::IsDialog',
             'ui::IsHeading',
-            'ui::IsContainerWithSelectableChildren',
+            'ui::IsPlatformDocument',
             'ui::IsTableLike',
             'ui::IsTableRow',
-            'ui::IsCellOrTableHeader',
             'ui::IsTableHeader',
 
             # Blink uses UKM for logging e.g. always-on leak detection (crbug/757374)
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 9c806da..4e40e81 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -168,6 +168,14 @@
 crbug.com/591099 external/wpt/css/css-sizing/intrinsic-percent-replaced-dynamic-008.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-sizing/whitespace-and-break.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-sizing/ortho-writing-mode-001.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-001.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-002.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-003.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-004.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-005.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-006.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-007.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-008.html [ Failure ]
 
 ### external/wpt/css/css-text/boundary-shaping/
 crbug.com/591099 external/wpt/css/css-text/boundary-shaping/boundary-shaping-001.html [ Failure ]
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index ea87f4a..6109315 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -2235,7 +2235,6 @@
 crbug.com/626703 external/wpt/html/browsers/windows/restore-window-name-manual.https.html [ Skip ]
 crbug.com/626703 external/wpt/fullscreen/api/element-request-fullscreen-cross-origin-manual.sub.html [ Skip ]
 crbug.com/626703 external/wpt/pointerevents/html/pointerevent_drag_interaction-manual.html [ Skip ]
-crbug.com/626703 external/wpt/uievents/order-of-events/focus-events/focus-contained-manual.html [ Skip ]
 crbug.com/626703 external/wpt/pointerevents/pointerevent_multiple_primary_pointers_boundary_events-manual.html [ Skip ]
 crbug.com/626703 external/wpt/uievents/order-of-events/focus-events/legacy-manual.html [ Skip ]
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 1cfb5e4..c847cac 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -234,6 +234,11 @@
 crbug.com/1126305 wpt_internal/prerender/* [ Skip ]
 crbug.com/1126305 virtual/prerender/wpt_internal/prerender/* [ Pass ]
 
+# This test currently fails since prerendering activation isn't fully plumbed
+# into the renderer yet.
+crbug.com/1183320 virtual/prerender/wpt_internal/prerender/state_and_event.html [ Timeout ]
+
+
 crbug.com/1185506 external/wpt/css/css-backgrounds/background-margin-iframe-root.html [ Pass Failure ]
 crbug.com/1185506 external/wpt/css/css-backgrounds/background-margin-root.html [ Pass Failure ]
 crbug.com/1185506 external/wpt/css/css-backgrounds/background-margin-transformed-root.html [ Pass Failure ]
@@ -548,6 +553,14 @@
 #### external/wpt/css/css-sizing
 crbug.com/1164135 external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-025.html [ Failure ]
 crbug.com/1164135 external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-026.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-001.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-002.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-003.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-004.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-005.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-006.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-007.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-008.html [ Failure ]
 
 crbug.com/1008951 external/wpt/css/css-text-decor/text-decoration-subelements-002.html [ Failure ]
 crbug.com/1008951 external/wpt/css/css-text-decor/text-decoration-subelements-003.html [ Failure ]
@@ -1506,6 +1519,8 @@
 crbug.com/821455 editing/pasteboard/drag-files-to-editable-element.html [ Failure ]
 
 crbug.com/1170052 external/wpt/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html [ Pass Timeout ]
+crbug.com/1174937 virtual/feature-policy-permissions/external/wpt/mediacapture-streams/MediaStream-clone.https.html [ Crash ]
+crbug.com/1174937 external/wpt/mediacapture-streams/MediaStream-clone.https.html [ Crash ]
 
 crbug.com/766135 fast/dom/Window/redirect-with-timer.html [ Timeout Pass ]
 
@@ -2516,8 +2531,6 @@
 crbug.com/626703 [ Linux ] external/wpt/css/css-color/hsl-008.html [ Failure ]
 crbug.com/626703 [ Mac ] external/wpt/css/css-color/hsl-008.html [ Pass Failure ]
 crbug.com/626703 [ Win ] external/wpt/css/css-color/hsl-008.html [ Failure ]
-crbug.com/626703 [ Mac10.13 ] virtual/feature-policy-permissions/external/wpt/mediacapture-streams/MediaStream-clone.https.html [ Crash ]
-crbug.com/626703 [ Mac11.0 ] virtual/feature-policy-permissions/external/wpt/mediacapture-streams/MediaStream-clone.https.html [ Crash ]
 crbug.com/626703 virtual/system-color-compute/external/wpt/css/css-color/hsl-008.html [ Failure ]
 crbug.com/626703 external/wpt/css/selectors/focus-visible-007.html [ Timeout ]
 crbug.com/626703 external/wpt/uievents/keyboard/modifier-keys-combinations.html [ Timeout ]
@@ -5632,6 +5645,9 @@
 crbug.com/1178160 virtual/threaded-prefer-compositing/external/wpt/css/cssom-view/MediaQueryList-extends-EventTarget.html [ Pass Failure ]
 crbug.com/1178160 virtual/threaded-prefer-compositing/external/wpt/css/cssom-view/MediaQueryList-extends-EventTarget-interop.html [ Pass Failure ]
 
+# Failing on Webkit Linux Leak
+crbug.com/1174806 external/wpt/css/selectors/focus-visible-006.html [ Pass Failure ]
+
 # No support for key combinations like Alt + c in testdriver.Actions for content_shell
 crbug.com/893480 external/wpt/uievents/interface/keyboard-accesskey-click-event.html [ Timeout ]
 
diff --git a/third_party/blink/web_tests/accessibility/image-map1-expected.txt b/third_party/blink/web_tests/accessibility/image-map1-expected.txt
index c777793..a14b102c 100644
--- a/third_party/blink/web_tests/accessibility/image-map1-expected.txt
+++ b/third_party/blink/web_tests/accessibility/image-map1-expected.txt
@@ -2,7 +2,7 @@
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
-PASS container.role is 'AXRole: AXImageMap'
+PASS container.role is 'AXRole: AXImage'
 PASS container.childAtIndex(0).role is 'AXRole: AXLink'
 PASS container.childAtIndex(0).name is 'Link1'
 PASS container.childAtIndex(1).role is 'AXRole: AXLink'
diff --git a/third_party/blink/web_tests/accessibility/image-map1.html b/third_party/blink/web_tests/accessibility/image-map1.html
index ec8a37cb..fba21a7 100644
--- a/third_party/blink/web_tests/accessibility/image-map1.html
+++ b/third_party/blink/web_tests/accessibility/image-map1.html
@@ -30,7 +30,7 @@
             var body = accessibilityController.accessibleElementById('body');
             var container = body.childAtIndex(0);
 
-            shouldBe("container.role", "'AXRole: AXImageMap'");
+            shouldBe("container.role", "'AXRole: AXImage'");
 
 
             shouldBe("container.childAtIndex(0).role", "'AXRole: AXLink'");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/svg-transform-animation-ref.html b/third_party/blink/web_tests/external/wpt/css/css-animations/svg-transform-animation-ref.html
new file mode 100644
index 0000000..d8666e8a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/svg-transform-animation-ref.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<div style="width: 100px; height: 100px; position: relative; top: 100px; left: 100px;
+            background: green"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/svg-transform-animation.html b/third_party/blink/web_tests/external/wpt/css/css-animations/svg-transform-animation.html
new file mode 100644
index 0000000..b08629c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/svg-transform-animation.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Transform animation on SVG element with zoom</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/">
+<link rel="match" href="svg-transform-animation-ref.html">
+<style>
+@keyframes transform {
+  from {transform: translate(100px, 100px)}
+  to {transform: translate(100px, 100px)}
+}
+</style>
+<svg width="200" height="200">
+  <rect x="100" y="100" width="100" height="100" fill="red"/>
+  <rect style="animation: transform 2s infinite" x="0" y="0" width="100" height="100" fill="green"/>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-015.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-015.html
new file mode 100644
index 0000000..2bbcf8f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-015.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#abspos-breaking">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  #multicol {
+    columns: 2;
+    width: 100px;
+    height: 100px;
+    column-fill: auto;
+    column-gap: 0px;
+    background-color: red;
+  }
+  .rel {
+    height: 100px;
+    position: relative;
+  }
+  .abs {
+    position: absolute;
+    width: 50px;
+    background: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="multicol">
+  <div class="rel">
+    <div class="abs" style="top: 0px; height: 160px;"></div>
+    <div class="abs" style="top: 100px; height: 20px;"></div>
+  </div>
+  <div style="column-span:all; height: 20px; background: green;"></div>
+  <div style="height: 60px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-021.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-021.html
new file mode 100644
index 0000000..05ce3d9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-021.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>
+  Nested fragmentation for out-of-flow positioned elements create new columns.
+</title>
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#abspos-breaking">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .multicol {
+    column-count: 2;
+    column-fill: auto;
+    column-gap: 0px;
+    background-color: red;
+  }
+  #outer {
+    height: 100px;
+    width: 100px;
+  }
+  #inner {
+    width: 50px;
+  }
+  .rel {
+    position: relative;
+  }
+  .abs {
+    position: absolute;
+    height: 400px;
+    width: 25px;
+    top: 800px;
+    left: -200px;
+    background-color: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol" id="outer">
+  <div class="multicol" id="inner">
+    <div class="rel">
+      <div class="abs"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-001-ref.html
index 160914c..9d3a3f5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-001-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-001-ref.html
@@ -5,8 +5,8 @@
 <style>
   img {
     border: 1px solid black;
-    height: 30px;
-    width: 60px;
+    height: 40px;
+    width: 40px;
   }
 </style>
 <img src="/css/support/60x60-green.png">
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-001.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-001.html
index 7dcf110..b7c57bb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-001.html
@@ -1,6 +1,6 @@
 <!doctype html>
 <html class="reftest-wait">
-<title>CSS Test: Image size is updated properly when intrinsic size changes, even with a fixed width and height, if their min-size depends on their intrinsic size</title>
+<title>CSS Test: Image size is not altered when it loads when min/max-size depends on the transferred intrinsic sizes, not natural sizes</title>
 <link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
 <link rel="author" href="https://mozilla.org" title="Mozilla">
 <link rel="help" href="https://drafts.csswg.org/css-sizing/#sizing-values">
@@ -9,7 +9,7 @@
 <style>
   img {
     border: 1px solid black;
-    height: 30px;
+    height: 40px;
     width: 30px;
     min-width: min-content;
   }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-002.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-002.html
index 5447092..70f74476 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-002.html
@@ -1,6 +1,6 @@
 <!doctype html>
 <html class="reftest-wait">
-<title>CSS Test: Image size is updated properly when intrinsic size changes, even with a fixed width and height, if their min-size depends on their intrinsic size</title>
+<title>CSS Test: Image size is not altered when it loads when min/max-size depends on the transferred intrinsic sizes, not natural sizes</title>
 <link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
 <link rel="author" href="https://mozilla.org" title="Mozilla">
 <link rel="help" href="https://drafts.csswg.org/css-sizing/#sizing-values">
@@ -9,7 +9,7 @@
 <style>
   img {
     border: 1px solid black;
-    height: 30px;
+    height: 40px;
     width: 30px;
     min-width: max-content;
   }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-003-ref.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-003-ref.html
index 56b71a0..c3253f7 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-003-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-003-ref.html
@@ -5,8 +5,8 @@
 <style>
   img {
     border: 1px solid black;
-    height: 80px;
-    width: 60px;
+    height: 70px;
+    width: 70px;
   }
 </style>
 <img src="/css/support/60x60-green.png">
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-003.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-003.html
index 8da6b4c..b1c87e6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-003.html
@@ -1,6 +1,6 @@
 <!doctype html>
 <html class="reftest-wait">
-<title>CSS Test: Image size is updated properly when intrinsic size changes, even with a fixed width and height, if their max-size depends on their intrinsic size</title>
+<title>CSS Test: Image size is not altered when it loads when min/max-size depends on the transferred intrinsic sizes, not natural sizes</title>
 <link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
 <link rel="author" href="https://mozilla.org" title="Mozilla">
 <link rel="help" href="https://drafts.csswg.org/css-sizing/#sizing-values">
@@ -9,7 +9,7 @@
 <style>
   img {
     border: 1px solid black;
-    height: 80px;
+    height: 70px;
     width: 80px;
     max-width: min-content;
   }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-004.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-004.html
index b9955af..431bfa5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-004.html
@@ -1,6 +1,6 @@
 <!doctype html>
 <html class="reftest-wait">
-<title>CSS Test: Image size is updated properly when intrinsic size changes, even with a fixed width and height, if their max-size depends on their intrinsic size</title>
+<title>CSS Test: Image size is not altered when it loads when min/max-size depends on the transferred intrinsic sizes, not natural sizes</title>
 <link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
 <link rel="author" href="https://mozilla.org" title="Mozilla">
 <link rel="help" href="https://drafts.csswg.org/css-sizing/#sizing-values">
@@ -9,7 +9,7 @@
 <style>
   img {
     border: 1px solid black;
-    height: 80px;
+    height: 70px;
     width: 80px;
     max-width: max-content;
   }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-005-ref.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-005-ref.html
deleted file mode 100644
index 9964f07..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-005-ref.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!doctype html>
-<title>CSS Test Reference</title>
-<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
-<link rel="author" href="https://mozilla.org" title="Mozilla">
-<style>
-  img {
-    border: 1px solid black;
-    height: 60px;
-    width: 30px;
-  }
-</style>
-<img src="/css/support/60x60-green.png">
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-005.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-005.html
index 86a2a72f..a5f308e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-005.html
@@ -1,16 +1,16 @@
 <!doctype html>
 <html class="reftest-wait">
-<title>CSS Test: Image size is updated properly when intrinsic size changes, even with a fixed width and height, if their min-size depends on their intrinsic size</title>
+<title>CSS Test: Image size is not altered when it loads when min/max-size depends on the transferred intrinsic sizes, not natural sizes</title>
 <link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
 <link rel="author" href="https://mozilla.org" title="Mozilla">
 <link rel="help" href="https://drafts.csswg.org/css-sizing/#sizing-values">
 <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1546739">
-<link rel="match" href="image-min-max-content-intrinsic-size-change-005-ref.html">
+<link rel="match" href="image-min-max-content-intrinsic-size-change-001-ref.html">
 <style>
   img {
     border: 1px solid black;
     height: 30px;
-    width: 30px;
+    width: 40px;
     min-height: min-content;
     writing-mode: vertical-lr;
   }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-006.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-006.html
index ddb8924..68cbee7 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-006.html
@@ -1,16 +1,16 @@
 <!doctype html>
 <html class="reftest-wait">
-<title>CSS Test: Image size is updated properly when intrinsic size changes, even with a fixed width and height, if their min-size depends on their intrinsic size</title>
+<title>CSS Test: Image size is not altered when it loads when min/max-size depends on the transferred intrinsic sizes, not natural sizes</title>
 <link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
 <link rel="author" href="https://mozilla.org" title="Mozilla">
 <link rel="help" href="https://drafts.csswg.org/css-sizing/#sizing-values">
 <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1546739">
-<link rel="match" href="image-min-max-content-intrinsic-size-change-005-ref.html">
+<link rel="match" href="image-min-max-content-intrinsic-size-change-001-ref.html">
 <style>
   img {
     border: 1px solid black;
     height: 30px;
-    width: 30px;
+    width: 40px;
     min-height: max-content;
     writing-mode: vertical-lr;
   }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-007-ref.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-007-ref.html
deleted file mode 100644
index 8b9da1c9..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-007-ref.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!doctype html>
-<title>CSS Test Reference</title>
-<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
-<link rel="author" href="https://mozilla.org" title="Mozilla">
-<style>
-  img {
-    border: 1px solid black;
-    width: 80px;
-    height: 60px;
-  }
-</style>
-<img src="/css/support/60x60-green.png">
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-007.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-007.html
index 150052f..c1daabf 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-007.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-007.html
@@ -1,16 +1,16 @@
 <!doctype html>
 <html class="reftest-wait">
-<title>CSS Test: Image size is updated properly when intrinsic size changes, even with a fixed width and height, if their max-size depends on their intrinsic size</title>
+<title>CSS Test: Image size is not altered when it loads when min/max-size depends on the transferred intrinsic sizes, not natural sizes</title>
 <link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
 <link rel="author" href="https://mozilla.org" title="Mozilla">
 <link rel="help" href="https://drafts.csswg.org/css-sizing/#sizing-values">
 <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1546739">
-<link rel="match" href="image-min-max-content-intrinsic-size-change-007-ref.html">
+<link rel="match" href="image-min-max-content-intrinsic-size-change-003-ref.html">
 <style>
   img {
     border: 1px solid black;
     height: 80px;
-    width: 80px;
+    width: 70px;
     max-height: min-content;
     writing-mode: vertical-lr;
   }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-008.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-008.html
index 259e2f3..12f7d35a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-008.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/image-min-max-content-intrinsic-size-change-008.html
@@ -1,16 +1,16 @@
 <!doctype html>
 <html class="reftest-wait">
-<title>CSS Test: Image size is updated properly when intrinsic size changes, even with a fixed width and height, if their max-size depends on their intrinsic size</title>
+<title>CSS Test: Image size is not altered when it loads when min/max-size depends on the transferred intrinsic sizes, not natural sizes</title>
 <link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
 <link rel="author" href="https://mozilla.org" title="Mozilla">
 <link rel="help" href="https://drafts.csswg.org/css-sizing/#sizing-values">
 <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1546739">
-<link rel="match" href="image-min-max-content-intrinsic-size-change-007-ref.html">
+<link rel="match" href="image-min-max-content-intrinsic-size-change-003-ref.html">
 <style>
   img {
     border: 1px solid black;
     height: 80px;
-    width: 80px;
+    width: 70px;
     max-height: max-content;
     writing-mode: vertical-lr;
   }
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-006.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-006.html
index f607df25..c203c06f 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-006.html
@@ -45,16 +45,15 @@
     <span id="el" contenteditable>Focus me</span>
   </div>
   <script>
-    var actions_promise;
+    setup({ explicit_done: true });
+
     async_test(function(t) {
       el.addEventListener("focus", t.step_func_done(function() {
         assert_equals(getComputedStyle(el).outlineColor, "rgb(0, 128, 0)", `outlineColor for ${el.tagName}#${el.id} should be green`);
         assert_not_equals(getComputedStyle(el).backgroundColor, "rgb(255, 0, 0)", `backgroundColor for ${el.tagName}#${el.id} should NOT be red`);
-        // Make sure the test finishes after all the input actions are completed.
-        actions_promise.then( () => t.done() );
       }));
 
-      actions_promise = test_driver.click(el);
+      test_driver.click(el).then(done());
     }, "Focus should always match :focus-visible on content editable divs");
   </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/layout-instability/shift-while-scrolled.html b/third_party/blink/web_tests/external/wpt/layout-instability/shift-while-scrolled.html
index 88eeede..3cdd3571 100644
--- a/third_party/blink/web_tests/external/wpt/layout-instability/shift-while-scrolled.html
+++ b/third_party/blink/web_tests/external/wpt/layout-instability/shift-while-scrolled.html
@@ -26,9 +26,8 @@
   // Generate a layout shift.
   document.querySelector("#shift").style = "top: 60px";
 
-  // Impact region: width * (height - scrollTop + moveDistance)
-  const expectedScore = computeExpectedScore(
-      300 * (200 - 100 + 60), 60);
+  const moveDistanceInView = 100 - 60;
+  const expectedScore = computeExpectedScore(300 * 200, moveDistanceInView);
 
   await watcher.promise;
   assert_equals(watcher.score, expectedScore);
diff --git a/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counter-scroll-and-transform.html b/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counter-scroll-and-transform.html
new file mode 100644
index 0000000..b4e4a99
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counter-scroll-and-transform.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<title>Layout Instability: shift with counter scroll and transform not counted</title>
+<link rel="help" href="https://wicg.github.io/layout-instability/" />
+<style>
+.scroller {
+  overflow: scroll;
+  position: absolute;
+  left: 20px;
+  top: 20px;
+  width: 200px;
+  height: 200px;
+}
+.content {
+  width: 600px;
+  height: 600px;
+}
+.changer {
+  position: relative;
+  background: yellow;
+  left: 10px;
+  top: 100px;
+  width: 150px;
+  height: 150px;
+}
+
+</style>
+<div id="scroller1" class="scroller">
+  <div class="content">
+    <div id="changer1" class="changer"></div>
+  </div>
+</div>
+<div id="scroller2" class="scroller">
+  <div class="content">
+    <div id="changer2" class="changer"></div>
+  </div>
+</div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/util.js"></script>
+<script>
+
+promise_test(async () => {
+  const watcher = new ScoreWatcher;
+
+  // Wait for the initial render to complete.
+  await waitForAnimationFrames(2);
+
+  changer1.style.top = "250px";
+  changer1.style.transform = "translateY(-50px)";
+  // 250 - 50 = 200; old position is 100; hence scrollTop to counter is 100.
+  scroller1.scrollTop = 100;
+
+  changer2.style.left = "220px";
+  changer2.style.transform = "translateX(80px)";
+  // 220 + 80 = 300; old position is 10; hence scrollTop to counter is 290.
+  scroller2.scrollLeft = 290;
+
+  await waitForAnimationFrames(3);
+  assert_equals(watcher.score, 0);
+}, "Shift with counter scroll and transform not counted.");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll-2.html b/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll-2.html
new file mode 100644
index 0000000..d9972301
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll-2.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<title>Layout Instability: shift with counterscroll not counted, with 2 scrollers</title>
+<link rel="help" href="https://wicg.github.io/layout-instability/" />
+<style>
+.scroller {
+  overflow: scroll;
+  position: absolute;
+  left: 20px;
+  top: 20px;
+  width: 200px;
+  height: 200px;
+}
+.content {
+  width: 170px;
+  height: 600px;
+}
+.changer {
+  position: relative;
+  background: yellow;
+  left: 10px;
+  top: 100px;
+  width: 150px;
+  height: 150px;
+}
+
+</style>
+<div id="scroller1" class="scroller">
+  <div class="content">
+    <div id="changer1" class="changer"></div>
+  </div>
+</div>
+<div id="scroller2" class="scroller">
+  <div class="content">
+    <div id="changer2" class="changer"></div>
+  </div>
+</div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/util.js"></script>
+<script>
+
+promise_test(async () => {
+  const watcher = new ScoreWatcher;
+
+  // Wait for the initial render to complete.
+  await waitForAnimationFrames(2);
+
+  // Top goes from 100 to 200. scroll by 100 to counter it.
+  changer1.style.top = "200px";
+  scroller1.scrollTop = 100;
+  // Top goes from 100 to 300. scroll by 200 to counter it.
+  changer2.style.top = "300px";
+  scroller2.scrollTop = 200;
+
+  await waitForAnimationFrames(3);
+  assert_equals(watcher.score, 0);
+}, "Shift with counterscroll not counted, with 2 scrollers.");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll.html b/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll.html
index 8ad1a46..85a8ed9 100644
--- a/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll.html
+++ b/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll.html
@@ -52,5 +52,3 @@
 }, "Shift with counterscroll not counted.");
 
 </script>
-
-
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/cross-origin-status-codes.html b/third_party/blink/web_tests/external/wpt/resource-timing/cross-origin-status-codes.html
index e8ece5ff..512e437 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/cross-origin-status-codes.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/cross-origin-status-codes.html
@@ -1,6 +1,9 @@
 <!doctype html>
 <html>
 <head>
+<title>Resource Timing: PerformanceResourceTiming attributes shouldn't change
+  if the HTTP status code changes</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src=/common/get-host-info.sub.js></script>
@@ -15,34 +18,53 @@
 <script id="script_404"></script>
 <script id="script_502"></script>
 <script>
-async_test(t => {
-  let destUrl = get_host_info().HTTP_REMOTE_ORIGIN + '/resource-timing/resources/';
-  let statusCodes = ['200', '307', '404', '502'];
-  statusCodes.forEach(status => {
-    document.getElementById('img_' + status).src = destUrl + 'status-code.py?status=' + status;
-    document.getElementById('script_' + status).src = destUrl + 'status-code.py?status=' + status + '&script=1';
-  });
-  let nameMap = {};
-  let firstEntry = null;
-  // We will check that the non-timestamp values of the entry match for all entries.
-  const keys = ['entryType', 'nextHopProtocol', 'transferSize', 'encodedBodySize', 'decodedBodySize'];
-  new PerformanceObserver(t.step_func(entryList => {
-    entryList.getEntries().forEach(entry => {
-      if (!entry.name.includes("status-code"))
-        return;
 
-      nameMap[entry.name] = true;
-      if (!firstEntry) {
-        firstEntry = entry;
-      } else {
-        keys.forEach(key => {
-          assert_equals(entry[key], firstEntry[key], `Discernible difference in ${key} for ${entry.name}`);
-        });
-      }
-    });
-    if (Object.keys(nameMap).length === 8) {
-      t.done();
-    }
-  })).observe({entryTypes: ['resource']});
+function listenForPerformanceEntries(num_expected) {
+  return new Promise(resolve => {
+    let results = [];
+    new PerformanceObserver(entryList => {
+      entryList.getEntries().forEach(entry => {
+        if (!entry.name.includes("status-code"))
+          return;
+
+        results.push(entry);
+        if (results.length == num_expected) {
+          resolve(results);
+        }
+      });
+    }).observe({entryTypes: ['resource']});
+  });
+}
+
+promise_test(async t => {
+  const destUrl = get_host_info().HTTP_REMOTE_ORIGIN + '/resource-timing/resources/';
+  const statusCodes = ['200', '307', '404', '502'];
+
+  let expected_entry_count = 0;
+  statusCodes.forEach(status => {
+    document.getElementById(`img_${status}`).src = `${destUrl}status-code.py?status=${status}`;
+    document.getElementById(`script_${status}`).src = `${destUrl}status-code.py?status=${status}&script=1`;
+    expected_entry_count += 2;
+  });
+
+  const entries = await listenForPerformanceEntries(expected_entry_count);
+
+  // We will check that the non-timestamp values of the entry match for all
+  // entries.
+  const keys = [
+    'entryType',
+    'nextHopProtocol',
+    'transferSize',
+    'encodedBodySize',
+    'decodedBodySize',
+  ];
+
+  const first = entries[0];
+  entries.slice(1).forEach(entry => {
+    keys.forEach(attribute => {
+      assert_equals(entry[attribute], first[attribute],
+        `There must be no discernible difference for the ${attribute} ` +
+        `attribute but found a difference for the ${entry.name} resource.`);
+  })});
 }, "Make sure cross origin resource fetch failures with different status codes are indistinguishable");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/entry-attributes.html b/third_party/blink/web_tests/external/wpt/resource-timing/entry-attributes.html
index 013f4d7..5f330bb 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/entry-attributes.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/entry-attributes.html
@@ -93,32 +93,61 @@
   ]);
 }
 
-promise_test(async () => {
-  // Clear out everything that isn't the one ResourceTiming entry under test.
-  performance.clearResourceTimings();
-  await load_image("resources/fake_responses.py#hash=1");
-  const entry_list = performance.getEntriesByType("resource");
-  if (entry_list.length != 1) {
-    throw new Error("There should be one entry for one resource");
-  }
-  const entry = entry_list[0];
-  assert_true(entry.name.includes('#hash=1'),
-    "There should be a hash in the resource name");
+function assert_same_origin_redirected_resource(entry) {
+  assert_positive(entry, [
+    "redirectStart",
+  ]);
 
-  assert_http_resource(entry);
-}, "Image resources should generate conformant entries");
+  assert_equals(entry.redirectStart, entry.startTime,
+    "redirectStart should be equal to startTime");
 
-promise_test(async () => {
-  // Clear out everything that isn't the one ResourceTiming entry under test.
-  performance.clearResourceTimings();
-  await load_font("/fonts/Ahem.ttf");
-  const entry_list = performance.getEntriesByType("resource");
-  if (entry_list.length != 1) {
-    throw new Error("There should be one entry for one resource");
-  }
-  assert_http_resource(entry_list[0]);
-}, "Font resources should generate conformant entries");
+  assert_ordered(entry, [
+    "redirectStart",
+    "redirectEnd",
+    "fetchStart",
+    "domainLookupStart",
+    "domainLookupEnd",
+    "connectStart",
+  ]);
+}
 
+// Given a resource-loader and a PerformanceResourceTiming validator, loads a
+// resource and validates the resulting entry.
+function attribute_test(load_resource, validate, test_label) {
+  promise_test(
+    async () => {
+      // Clear out everything that isn't the one ResourceTiming entry under test.
+      performance.clearResourceTimings();
+
+      await load_resource();
+
+      const entry_list = performance.getEntriesByType("resource");
+      if (entry_list.length != 1) {
+        throw new Error(`There should be one entry for one resource (found ${entry_list.length})`);
+      }
+
+      validate(entry_list[0]);
+  }, test_label);
+}
+
+attribute_test(
+  () => load_image("resources/fake_responses.py#hash=1"),
+  entry => {
+    assert_true(entry.name.includes('#hash=1'),
+      "There should be a hash in the resource name");
+    assert_http_resource(entry);
+  },
+  "Image resources should generate conformant entries");
+
+attribute_test(
+  () => load_font("/fonts/Ahem.ttf"),
+  assert_http_resource,
+  "Font resources should generate conformant entries");
+
+attribute_test(
+  () => load_image("/common/redirect.py?location=resources/fake_responses.py"),
+  assert_same_origin_redirected_resource,
+  "Same-origin redirects should populate redirectStart/redirectEnd");
 </script>
 </head>
 <body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resource_timing_same_origin_redirect.html b/third_party/blink/web_tests/external/wpt/resource-timing/resource_timing_same_origin_redirect.html
deleted file mode 100644
index d9fbf94..0000000
--- a/third_party/blink/web_tests/external/wpt/resource-timing/resource_timing_same_origin_redirect.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-<meta charset="utf-8" />
-<title>This test validates the values of the redirectStart/End in resource timing for a same-origin resource redirect.</title>
-<link rel="author" title="Intel" href="http://www.intel.com/" />
-<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/webperftestharness.js"></script>
-<script src="resources/webperftestharnessextension.js"></script>
-
-<script>
-    setup({explicit_done: true});
-    function onload_test() {
-        const context = new PerformanceContext(performance);
-        const entry = context.getEntriesByName(document.getElementById('frameContext').src, 'resource')[0];
-
-        test_greater_than(entry.redirectStart, 0, 'redirectStart should be greater than 0 in same-origin redirect.');
-        test_equals(entry.redirectStart, entry.startTime, 'redirectStart should be equal to startTime in same-origin redirect.');
-        test_noless_than(entry.redirectEnd, entry.redirectStart, 'redirectEnd should be no less than redirectStart in same-origin redirect.');
-        test_noless_than(entry.fetchStart, entry.redirectEnd, 'fetchStart should be no less than redirectEnd in same-origin redirect.');
-        done();
-    }
-</script>
-
-</head>
-<body>
-<iframe id="frameContext" src="" style="width: 250px; height: 250px;"></iframe>
-<script>
-    let destUrl = '/common/redirect.py';
-    destUrl += '?location=/resource-timing/resources/blank_page_green.htm';
-
-    const frameContext = document.getElementById('frameContext');
-    frameContext.onload = onload_test;
-    frameContext.src = destUrl;
-</script>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/external/wpt/svg/types/elements/SVGGeometryElement-rect-expected.txt b/third_party/blink/web_tests/external/wpt/svg/types/elements/SVGGeometryElement-rect-expected.txt
deleted file mode 100644
index 5594cfb2..0000000
--- a/third_party/blink/web_tests/external/wpt/svg/types/elements/SVGGeometryElement-rect-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-PASS getTotalLength and getPointAtLength do not take pathLength into account
-FAIL getPointAtLength() returns instance of DOMPoint assert_true: expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/svg/types/elements/SVGGeometryElement-rect.svg b/third_party/blink/web_tests/external/wpt/svg/types/elements/SVGGeometryElement-rect.svg
index d4b278f..93d25b6 100644
--- a/third_party/blink/web_tests/external/wpt/svg/types/elements/SVGGeometryElement-rect.svg
+++ b/third_party/blink/web_tests/external/wpt/svg/types/elements/SVGGeometryElement-rect.svg
@@ -26,7 +26,7 @@
   }, 'getTotalLength and getPointAtLength do not take pathLength into account');
 
   test(function() {
-    assert_true(box.getPointAtLength(210) instanceof DOMPoint);
-  }, 'getPointAtLength() returns instance of DOMPoint');
+    assert_true(box.getPointAtLength(210) instanceof SVGPoint);
+  }, 'getPointAtLength() returns instance of SVGPoint');
   ]]></script>
 </svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedRect-expected.txt b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedRect-expected.txt
deleted file mode 100644
index cdc41ca..0000000
--- a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedRect-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL SVGAnimatedRect interface - utilizing the viewBox property of SVGSVGElement assert_true: expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedRect.html b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedRect.html
index eb5bb1db..1dfbc77 100644
--- a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedRect.html
+++ b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedRect.html
@@ -10,9 +10,9 @@
 
   // Check initial viewBox value.
   assert_true(svgElement.viewBox instanceof SVGAnimatedRect);
-  assert_true(svgElement.viewBox.baseVal instanceof DOMRect);
+  assert_true(svgElement.viewBox.baseVal instanceof SVGRect);
   assert_equals(svgElement.viewBox.baseVal.x, 0);
-  assert_true(svgElement.viewBox.animVal instanceof DOMRectReadOnly);
+  assert_true(svgElement.viewBox.animVal instanceof SVGRect);
 
   // Check that rects are dynamic, caching value in a local variable and modifying it, should take effect.
   var numRef = svgElement.viewBox.baseVal;
@@ -29,6 +29,6 @@
   assert_equals(svgElement.viewBox.baseVal.x, 100);
 
   // Check that the viewBox baseVal type has not been changed.
-  assert_true(svgElement.viewBox.baseVal instanceof DOMRect);
+  assert_true(svgElement.viewBox.baseVal instanceof SVGRect);
 });
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGGraphicsElement-expected.txt b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGGraphicsElement-expected.txt
deleted file mode 100644
index 402bbd5e..0000000
--- a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGGraphicsElement-expected.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-This is a testharness.js-based test.
-FAIL getBBox() returns instance of DOMRect assert_true: expected true got false
-FAIL getCTM() returns instance of DOMMatrix assert_true: expected true got false
-FAIL getScreenCTM() returns instance of DOMMatrix assert_true: expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGGraphicsElement.svg b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGGraphicsElement.svg
index 8d5808f0..893882b 100644
--- a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGGraphicsElement.svg
+++ b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGGraphicsElement.svg
@@ -11,15 +11,15 @@
     var el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
 
     test(function() {
-        assert_true(el.getBBox() instanceof DOMRect);
-    }, 'getBBox() returns instance of DOMRect');
+        assert_true(el.getBBox() instanceof SVGRect);
+    }, 'getBBox() returns instance of SVGRect');
 
     test(function() {
-        assert_true(el.getCTM() instanceof DOMMatrix);
-    }, 'getCTM() returns instance of DOMMatrix');
+        assert_true(el.getCTM() instanceof SVGMatrix);
+    }, 'getCTM() returns instance of SVGMatrix');
 
     test(function() {
-        assert_true(el.getScreenCTM() instanceof DOMMatrix);
-    }, 'getScreenCTM() returns instance of DOMMatrix');
+        assert_true(el.getScreenCTM() instanceof SVGMatrix);
+    }, 'getScreenCTM() returns instance of SVGMatrix');
   ]]></script>
 </svg>
diff --git a/third_party/blink/web_tests/external/wpt/uievents/order-of-events/focus-events/focus-contained-manual.html b/third_party/blink/web_tests/external/wpt/uievents/order-of-events/focus-events/focus-contained.html
similarity index 72%
rename from third_party/blink/web_tests/external/wpt/uievents/order-of-events/focus-events/focus-contained-manual.html
rename to third_party/blink/web_tests/external/wpt/uievents/order-of-events/focus-events/focus-contained.html
index 3f6e584..8df612c1 100644
--- a/third_party/blink/web_tests/external/wpt/uievents/order-of-events/focus-events/focus-contained-manual.html
+++ b/third_party/blink/web_tests/external/wpt/uievents/order-of-events/focus-events/focus-contained.html
@@ -4,9 +4,11 @@
   <meta charset="utf-8">
   <title>Focus-related events should fire in the correct order</title>
   <link rel="help" href="https://w3c.github.io/uievents/#events-focusevent-event-order">
-  <meta name="flags" content="interact">
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
+  <script src="/resources/testdriver.js"></script>
+  <script src="/resources/testdriver-actions.js"></script>
+  <script src="/resources/testdriver-vendor.js"></script>
   <script src="/uievents/resources/eventrecorder.js"></script>
 </head>
 
@@ -35,9 +37,10 @@
   "focusin",
   "focusout"
 ];
-window.onload = function () {
+window.onload = async function () {
   var a = document.getElementById("a");
   var b = document.getElementById("b");
+  var button = document.getElementById("done");
   var inputs = [a, b];
   EventRecorder.configure({
       objectMap: {
@@ -48,22 +51,22 @@
 
   EventRecorder.addEventListenersForNodes(relevantEvents, inputs, stopPropagation);
   var expected = [
-    {type: "focusin", target: "b"},
     {type: "focus", target: "b"},
-    {type: "focusout", target: "b"},
-    {type: "focusin", target: "a"},
+    {type: "focusin", target: "b"},
     {type: "blur", target: "b"},
-    {type: "focus", target: "a"},
-    {type: "focusout", target: "a"},
-    {type: "focusin", target: "b"},
-    {type: "blur", target: "a"},
-    {type: "focus", target: "b"},
     {type: "focusout", target: "b"},
-    {type: "blur", target: "b"}
+    {type: "focus", target: "a"},
+    {type: "focusin", target: "a"},
+    {type: "blur", target: "a"},
+    {type: "focusout", target: "a"},
+    {type: "focus", target: "b"},
+    {type: "focusin", target: "b"},
+    {type: "blur", target: "b"},
+    {type: "focusout", target: "b"}
   ];
 
   async_test(function(t) {
-    document.getElementById("done").addEventListener("click", function () {
+    button.addEventListener("click", function () {
       t.step(function () {
         assert_true(EventRecorder.checkRecords(expected));
         t.done();
@@ -71,6 +74,21 @@
     }, false);
   }, "Focus-related events should fire in the correct order");
   EventRecorder.start();
+
+  await new test_driver.Actions()
+    .pointerMove(0, 0, {origin: b})
+    .pointerDown()
+    .pointerUp()
+    .pointerMove(0, 0, {origin: a})
+    .pointerDown()
+    .pointerUp()
+    .pointerMove(0, 0, {origin: b})
+    .pointerDown()
+    .pointerUp()
+    .pointerMove(0, 0, {origin: button})
+    .pointerDown()
+    .pointerUp()
+    .send();
 };
 </script>
 </html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt_automation/html/editing/dnd/events/relatedTarget-attribute-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/html/editing/dnd/events/relatedTarget-attribute-manual-automation.js
index 4bed7c9..5dc61d84 100644
--- a/third_party/blink/web_tests/external/wpt_automation/html/editing/dnd/events/relatedTarget-attribute-manual-automation.js
+++ b/third_party/blink/web_tests/external/wpt_automation/html/editing/dnd/events/relatedTarget-attribute-manual-automation.js
@@ -1,6 +1,48 @@
-importAutomationScript('/pointerevents/pointerevent_common_input.js');
+const boundaryOffset = 2;
+
+function scrollPageIfNeeded(targetSelector, targetDocument) {
+  var target = targetDocument.querySelector(targetSelector);
+  var targetRect = target.getBoundingClientRect();
+  if (targetRect.top < 0 || targetRect.left < 0 || targetRect.bottom > window.innerHeight || targetRect.right > window.innerWidth)
+    window.scrollTo(targetRect.left, targetRect.top);
+}
+
+// Drag and drop actions
+function mouseDragAndDropInTargets(targetSelectorList) {
+  return new Promise(function(resolve, reject) {
+    if (window.eventSender) {
+      scrollPageIfNeeded(targetSelectorList[0], document);
+      var target = document.querySelector(targetSelectorList[0]);
+      var targetRect = target.getBoundingClientRect();
+      var xPosition = targetRect.left + boundaryOffset;
+      var yPosition = targetRect.top + boundaryOffset;
+      eventSender.mouseMoveTo(xPosition, yPosition);
+      eventSender.mouseDown();
+      eventSender.leapForward(100);
+      for (var i = 1; i < targetSelectorList.length; i++) {
+        scrollPageIfNeeded(targetSelectorList[i], document);
+        target = document.querySelector(targetSelectorList[i]);
+        targetRect = target.getBoundingClientRect();
+        xPosition = targetRect.left + boundaryOffset;
+        yPosition = targetRect.top + boundaryOffset;
+        eventSender.mouseMoveTo(xPosition, yPosition);
+      }
+      eventSender.mouseUp();
+      resolve();
+    } else {
+      reject();
+    }
+  });
+}
 
 function inject_input() {
     return mouseDragAndDropInTargets(['#draggable', '#outerdiv', '#innerdiv', '#outerdiv']);
 }
 
+{
+  var pointerevent_automation = async_test("PointerEvent Automation");
+  // Defined in every test and should return a promise that gets resolved when input is finished.
+  inject_input().then(function() {
+    pointerevent_automation.done();
+  });
+}
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/html/pointerevent_drag_interaction-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/html/pointerevent_drag_interaction-manual-automation.js
index abc25c3..2b22d68 100644
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/html/pointerevent_drag_interaction-manual-automation.js
+++ b/third_party/blink/web_tests/external/wpt_automation/pointerevents/html/pointerevent_drag_interaction-manual-automation.js
@@ -1,5 +1,3 @@
-importAutomationScript('/pointerevents/pointerevent_common_input.js');
-
 function inject_input() {
   return new Promise(function(resolve, reject) {
       for (var i=0; i<3; i++) {
@@ -15,3 +13,11 @@
       resolve();
   });
 }
+
+{
+  var pointerevent_automation = async_test("PointerEvent Automation");
+  // Defined in every test and should return a promise that gets resolved when input is finished.
+  inject_input().then(function() {
+    pointerevent_automation.done();
+  });
+}
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_common_input.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_common_input.js
deleted file mode 100644
index 1c4da58..0000000
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_common_input.js
+++ /dev/null
@@ -1,400 +0,0 @@
-// This file contains the commonly used functions in pointerevent tests.
-
-const scrollOffset = 20;
-const boundaryOffset = 2;
-
-function scrollPageIfNeeded(targetSelector, targetDocument) {
-  var target = targetDocument.querySelector(targetSelector);
-  var targetRect = target.getBoundingClientRect();
-  if (targetRect.top < 0 || targetRect.left < 0 || targetRect.bottom > window.innerHeight || targetRect.right > window.innerWidth)
-    window.scrollTo(targetRect.left, targetRect.top);
-}
-
-function waitForCompositorCommit() {
-  return new Promise((resolve) => {
-    // For now, we just rAF twice. It would be nice to have a proper mechanism
-    // for this.
-    window.requestAnimationFrame(() => {
-      window.requestAnimationFrame(resolve);
-    });
-  });
-}
-
-// Mouse inputs.
-function mouseMoveToDocument() {
-  return new Promise(function(resolve, reject) {
-    if (window.chrome && chrome.gpuBenchmarking) {
-      chrome.gpuBenchmarking.pointerActionSequence(
-          [{
-            source: 'mouse',
-            actions: [{name: 'pointerMove', x: 0, y: 0}]
-          }],
-          resolve);
-    } else {
-      reject();
-    }
-  });
-}
-
-function mouseMoveIntoTarget(targetSelector, targetFrame) {
-  var targetDocument = document;
-  var frameLeft = 0;
-  var frameTop = 0;
-  if (targetFrame !== undefined) {
-    targetDocument = targetFrame.contentDocument;
-    var frameRect = targetFrame.getBoundingClientRect();
-    frameLeft = frameRect.left;
-    frameTop = frameRect.top;
-  }
-  return new Promise(function(resolve, reject) {
-    if (window.chrome && chrome.gpuBenchmarking) {
-      scrollPageIfNeeded(targetSelector, targetDocument);
-      var target = targetDocument.querySelector(targetSelector);
-      var targetRect = target.getBoundingClientRect();
-      var xPosition = frameLeft + targetRect.left + boundaryOffset;
-      var yPosition = frameTop + targetRect.top + boundaryOffset;
-      chrome.gpuBenchmarking.pointerActionSequence(
-          [{
-            source: 'mouse',
-            actions:
-                [{name: 'pointerMove', x: xPosition, y: yPosition}]
-          }],
-          resolve);
-    } else {
-      reject();
-    }
-  });
-}
-
-function mouseClickInTarget(targetSelector, targetFrame, button, shouldScrollToTarget = true) {
-  var targetDocument = document;
-  var frameLeft = 0;
-  var frameTop = 0;
-  // Initialize the button value to left button.
-  if (button === undefined) {
-    button = 0;
-  }
-  if (targetFrame !== undefined) {
-    targetDocument = targetFrame.contentDocument;
-    var frameRect = targetFrame.getBoundingClientRect();
-    frameLeft = frameRect.left;
-    frameTop = frameRect.top;
-  }
-  return new Promise(function(resolve, reject) {
-    if (window.chrome && chrome.gpuBenchmarking) {
-      if (shouldScrollToTarget)
-        scrollPageIfNeeded(targetSelector, targetDocument);
-      var target = targetDocument.querySelector(targetSelector);
-      var targetRect = target.getBoundingClientRect();
-      var xPosition = frameLeft + targetRect.left + boundaryOffset;
-      var yPosition = frameTop + targetRect.top + boundaryOffset;
-      chrome.gpuBenchmarking.pointerActionSequence(
-          [{
-            source: 'mouse',
-            actions: [
-              {name: 'pointerMove', x: xPosition, y: yPosition},
-              {name: 'pointerDown', x: xPosition, y: yPosition, button: button},
-              {name: 'pointerUp', button: button}
-            ]
-          }],
-          resolve);
-    } else {
-      reject();
-    }
-  });
-}
-
-function mouseDragInTargets(targetSelectorList, button) {
-  return new Promise(function(resolve, reject) {
-    if (window.chrome && chrome.gpuBenchmarking) {
-      // Initialize the button value to left button.
-      if (button === undefined) {
-        button = 0;
-      }
-      scrollPageIfNeeded(targetSelectorList[0], document);
-      var target = document.querySelector(targetSelectorList[0]);
-      var targetRect = target.getBoundingClientRect();
-      var xPosition = targetRect.left + boundaryOffset;
-      var yPosition = targetRect.top + boundaryOffset;
-      var pointerActions = [{'source': 'mouse'}];
-      var pointerAction = pointerActions[0];
-      pointerAction.actions = [];
-      pointerAction.actions.push(
-          {name: 'pointerDown', x: xPosition, y: yPosition, button: button});
-      for (var i = 1; i < targetSelectorList.length; i++) {
-        scrollPageIfNeeded(targetSelectorList[i], document);
-        target = document.querySelector(targetSelectorList[i]);
-        targetRect = target.getBoundingClientRect();
-        xPosition = targetRect.left + boundaryOffset;
-        yPosition = targetRect.top + boundaryOffset;
-        pointerAction.actions.push(
-            {name: 'pointerMove', x: xPosition, y: yPosition, button: button});
-      }
-      pointerAction.actions.push({name: 'pointerUp', button: button});
-      chrome.gpuBenchmarking.pointerActionSequence(pointerActions, resolve);
-    } else {
-      reject();
-    }
-  });
-}
-
-function mouseDragInTarget(targetSelector) {
-  return mouseDragInTargets([targetSelector, targetSelector]);
-}
-
-function smoothScrollBy(scrollOffset, xPosition, yPosition, direction, source, speed, preciseScrollingDeltas) {
-  return new Promise(function(resolve, reject) {
-    if (window.chrome && chrome.gpuBenchmarking) {
-      var scrollOffsetX = 0;
-      var scrollOffsetY = 0;
-      if (direction == "down") {
-        scrollOffsetY = scrollOffset;
-      } else if (direction == "up") {
-        scrollOffsetY = -scrollOffset;
-      } else if (direction == "right") {
-        scrollOffsetX = scrollOffset;
-      } else if (direction == "left") {
-        scrollOffsetX = -scrollOffset;
-      } else if (direction == "upleft") {
-        scrollOffsetX = -scrollOffset;
-        scrollOffsetY = -scrollOffset;
-      } else if (direction == "upright") {
-        scrollOffsetX = scrollOffset;
-        scrollOffsetY = -scrollOffset;
-      } else if (direction == "downleft") {
-        scrollOffsetX = -scrollOffset;
-        scrollOffsetY = scrollOffset;
-      } else if (direction == "downright") {
-        scrollOffsetX = scrollOffset;
-        scrollOffsetY = scrollOffset;
-      }
-      chrome.gpuBenchmarking.smoothScrollByXY(scrollOffsetX, scrollOffsetY, resolve, xPosition,
-        yPosition, source, speed, preciseScrollingDeltas);
-    } else {
-      reject();
-    }
-  });
-}
-
-function mouseWheelScroll(targetSelector, direction) {
-  scrollPageIfNeeded(targetSelector, document);
-  var target = document.querySelector(targetSelector);
-  var targetRect = target.getBoundingClientRect();
-  var xPosition = targetRect.left + boundaryOffset;
-  var yPosition = targetRect.top + boundaryOffset;
-  const SPEED_INSTANT = 400000;
-  const PRECISE_SCROLLING_DELTAS = false;
-  return smoothScrollBy(scrollOffset, xPosition, yPosition, direction, chrome.gpuBenchmarking.TOUCHPAD_INPUT, SPEED_INSTANT, PRECISE_SCROLLING_DELTAS);
-}
-
-// Touch inputs.
-function touchTapInTarget(targetSelector, targetFrame) {
-  var targetDocument = document;
-  var frameLeft = 0;
-  var frameTop = 0;
-  if (targetFrame !== undefined) {
-    targetDocument = targetFrame.contentDocument;
-    var frameRect = targetFrame.getBoundingClientRect();
-    frameLeft = frameRect.left;
-    frameTop = frameRect.top;
-  }
-  return new Promise(function(resolve, reject) {
-    if (window.chrome && chrome.gpuBenchmarking) {
-      scrollPageIfNeeded(targetSelector, targetDocument);
-      var target = targetDocument.querySelector(targetSelector);
-      var targetRect = target.getBoundingClientRect();
-      var xPosition = frameLeft + targetRect.left + boundaryOffset;
-      var yPosition = frameTop + targetRect.top + boundaryOffset;
-      chrome.gpuBenchmarking.pointerActionSequence( [
-        {source: 'touch',
-         actions: [
-            { name: 'pointerDown', x: xPosition, y: yPosition },
-            { name: 'pointerUp' }
-        ]}], resolve);
-    } else {
-      reject();
-    }
-  });
-}
-
-function pointerDragInTarget(pointerType, targetSelector, direction) {
-  return new Promise(function(resolve, reject) {
-    if (window.chrome && chrome.gpuBenchmarking) {
-      scrollPageIfNeeded(targetSelector, document);
-      var target = document.querySelector(targetSelector);
-      var targetRect = target.getBoundingClientRect();
-      var xPosition1 = targetRect.left + boundaryOffset + scrollOffset;
-      var yPosition1 = targetRect.top + boundaryOffset + scrollOffset;
-      var xPosition2 = xPosition1;
-      var yPosition2 = yPosition1;
-      var xPosition3 = xPosition1;
-      var yPosition3 = yPosition1;
-      if (direction == "down") {
-        yPosition1 -= scrollOffset;
-        yPosition3 += scrollOffset;
-      } else if (direction == "up") {
-        yPosition1 += scrollOffset;
-        yPosition3 -= scrollOffset;
-      } else if (direction == "right") {
-        xPosition1 -= scrollOffset;
-        xPosition3 += scrollOffset;
-      } else if (direction == "left") {
-        xPosition1 += scrollOffset;
-        xPosition3 -= scrollOffset;
-      } else {
-        throw("drag direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'");
-      }
-
-      // Ensure the compositor is aware of any scrolling done in
-      // |scrollPageIfNeeded| before sending the input events.
-      waitForCompositorCommit().then(() => {
-        chrome.gpuBenchmarking.pointerActionSequence( [
-          {source: pointerType,
-           actions: [
-              { name: 'pointerDown', x: xPosition1, y: yPosition1 },
-              { name: 'pointerMove', x: xPosition2, y: yPosition2 },
-              { name: 'pointerMove', x: xPosition3, y: yPosition3 },
-              { name: 'pause', duration: 100 },
-              { name: 'pointerUp' }
-          ]}], resolve);
-      });
-    } else {
-      reject();
-    }
-  });
-}
-
-function touchScrollInTarget(targetSelector, direction) {
-  if (direction == "down")
-    direction = "up";
-  else if (direction == "up")
-    direction = "down";
-  else if (direction == "right")
-    direction = "left";
-  else if (direction == "left")
-    direction = "right";
-  return pointerDragInTarget('touch', targetSelector, direction);
-}
-
-function pinchZoomInTarget(targetSelector, scale) {
-  return new Promise(function(resolve, reject) {
-    if (window.chrome && chrome.gpuBenchmarking) {
-      scrollPageIfNeeded(targetSelector, document);
-      var target = document.querySelector(targetSelector);
-      var targetRect = target.getBoundingClientRect();
-      var xPosition = targetRect.left + (targetRect.width/2);
-      var yPosition1 = targetRect.top + (targetRect.height/2) - 10;
-      var yPosition2 = targetRect.top + (targetRect.height/2) + 10;
-      var pointerActions = [{'source': 'touch'}, {'source': 'touch'}];
-      var pointerAction1 = pointerActions[0];
-      var pointerAction2 = pointerActions[1];
-      pointerAction1.actions = [];
-      pointerAction2.actions = [];
-      pointerAction1.actions.push(
-          {name: 'pointerDown', x: xPosition, y: yPosition1});
-      pointerAction2.actions.push(
-          {name: 'pointerDown', x: xPosition, y: yPosition2});
-      for (var offset = 10; offset < 80; offset += 10) {
-        pointerAction1.actions.push({
-          name: 'pointerMove',
-          x: xPosition,
-          y: (yPosition1 - offset)
-        });
-        pointerAction2.actions.push({
-          name: 'pointerMove',
-          x: xPosition,
-          y: (yPosition2 + offset)
-        });
-      }
-      pointerAction1.actions.push({name: 'pointerUp'});
-      pointerAction2.actions.push({name: 'pointerUp'});
-      // Ensure the compositor is aware of any scrolling done in
-      // |scrollPageIfNeeded| before sending the input events.
-      waitForCompositorCommit().then(() => {
-        chrome.gpuBenchmarking.pointerActionSequence(pointerActions, resolve);
-      });
-    } else {
-      reject();
-    }
-  });
-}
-
-// Pen inputs.
-function penMoveToDocument() {
-  return new Promise(function(resolve, reject) {
-    if (window.chrome && chrome.gpuBenchmarking) {
-      chrome.gpuBenchmarking.pointerActionSequence( [
-        {source: 'pen',
-         actions: [
-            { name: 'pointerMove', x: 0, y: 0 }
-        ]}], resolve);
-    } else {
-      reject();
-    }
-  });
-}
-
-function penEnterAndLeaveTarget(targetSelector, targetFrame) {
-  var targetDocument = document;
-  var frameLeft = 0;
-  var frameTop = 0;
-  if (targetFrame !== undefined) {
-    targetDocument = targetFrame.contentDocument;
-    var frameRect = targetFrame.getBoundingClientRect();
-    frameLeft = frameRect.left;
-    frameTop = frameRect.top;
-  }
-
-  return new Promise(function(resolve, reject) {
-    if (window.chrome && chrome.gpuBenchmarking) {
-      var target = targetDocument.querySelector(targetSelector);
-      var targetRect = target.getBoundingClientRect();
-      var xPosition = frameLeft + targetRect.left + boundaryOffset;
-      var yPosition = frameTop + targetRect.top + boundaryOffset;
-      chrome.gpuBenchmarking.pointerActionSequence( [
-        {source: 'pen',
-         actions: [
-            { name: 'pointerMove', x: xPosition, y: yPosition},
-            { name: 'pointerLeave' },
-        ]}], resolve);
-    } else {
-      reject();
-    }
-  });
-}
-
-// Drag and drop actions
-function mouseDragAndDropInTargets(targetSelectorList) {
-  return new Promise(function(resolve, reject) {
-    if (window.eventSender) {
-      scrollPageIfNeeded(targetSelectorList[0], document);
-      var target = document.querySelector(targetSelectorList[0]);
-      var targetRect = target.getBoundingClientRect();
-      var xPosition = targetRect.left + boundaryOffset;
-      var yPosition = targetRect.top + boundaryOffset;
-      eventSender.mouseMoveTo(xPosition, yPosition);
-      eventSender.mouseDown();
-      eventSender.leapForward(100);
-      for (var i = 1; i < targetSelectorList.length; i++) {
-        scrollPageIfNeeded(targetSelectorList[i], document);
-        target = document.querySelector(targetSelectorList[i]);
-        targetRect = target.getBoundingClientRect();
-        xPosition = targetRect.left + boundaryOffset;
-        yPosition = targetRect.top + boundaryOffset;
-        eventSender.mouseMoveTo(xPosition, yPosition);
-      }
-      eventSender.mouseUp();
-      resolve();
-    } else {
-      reject();
-    }
-  });
-}
-
-{
-  var pointerevent_automation = async_test("PointerEvent Automation");
-  // Defined in every test and should return a promise that gets resolved when input is finished.
-  inject_input().then(function() {
-    pointerevent_automation.done();
-  });
-}
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_pointerleave_pen-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_pointerleave_pen-manual-automation.js
index a3662056..2458566 100644
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_pointerleave_pen-manual-automation.js
+++ b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_pointerleave_pen-manual-automation.js
@@ -1,4 +1,38 @@
-importAutomationScript('/pointerevents/pointerevent_common_input.js');
+const boundaryOffset = 2;
+
+// Pen inputs.
+function penMoveToDocument() {
+  return new Promise(function(resolve, reject) {
+    if (window.chrome && chrome.gpuBenchmarking) {
+      chrome.gpuBenchmarking.pointerActionSequence( [
+        {source: 'pen',
+         actions: [
+            { name: 'pointerMove', x: 0, y: 0 }
+        ]}], resolve);
+    } else {
+      reject();
+    }
+  });
+}
+
+function penEnterAndLeaveTarget(targetSelector) {
+  return new Promise(function(resolve, reject) {
+    if (window.chrome && chrome.gpuBenchmarking) {
+      var target = document.querySelector(targetSelector);
+      var targetRect = target.getBoundingClientRect();
+      var xPosition = targetRect.left + boundaryOffset;
+      var yPosition = targetRect.top + boundaryOffset;
+      chrome.gpuBenchmarking.pointerActionSequence( [
+        {source: 'pen',
+         actions: [
+            { name: 'pointerMove', x: xPosition, y: yPosition},
+            { name: 'pointerLeave' },
+        ]}], resolve);
+    } else {
+      reject();
+    }
+  });
+}
 
 function inject_input() {
   return penEnterAndLeaveTarget('#target0').then(function() {
@@ -6,4 +40,12 @@
   }).then(function() {
     return penEnterAndLeaveTarget('#target0');
   });
+}
+
+{
+  var pointerevent_automation = async_test("PointerEvent Automation");
+  // Defined in every test and should return a promise that gets resolved when input is finished.
+  inject_input().then(function() {
+    pointerevent_automation.done();
+  });
 }
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_touch-action-rotated-divs_touch-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_touch-action-rotated-divs_touch-manual-automation.js
deleted file mode 100644
index 135ca0f5..0000000
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_touch-action-rotated-divs_touch-manual-automation.js
+++ /dev/null
@@ -1,24 +0,0 @@
-importAutomationScript('/pointerevents/pointerevent_common_input.js');
-
-function inject_input() {
-  return touchScrollInTarget('#target', 'down').then(function() {
-    return touchScrollInTarget('#target', 'up');
-  }).then(function() {
-    return touchScrollInTarget('#target', 'right');
-  }).then(function() {
-    return touchScrollInTarget('#target', 'left');
-  }).then(function() {
-    return touchTapInTarget('#btnDone');
-  }).then(function() {
-    return touchScrollInTarget('#target', 'down');
-  }).then(function() {
-    return touchScrollInTarget('#target', 'up');
-  }).then(function() {
-    return touchScrollInTarget('#target', 'right');
-  }).then(function() {
-    return touchScrollInTarget('#target', 'left');
-  }).then(function() {
-    return touchTapInTarget('#btnDone');
-  });
-}
-
diff --git a/third_party/blink/web_tests/fast/forms/calendar-picker/calendar-picker-appearance-ar.html b/third_party/blink/web_tests/fast/forms/calendar-picker/calendar-picker-appearance-ar.html
index 31e4ad2..d0ce847 100644
--- a/third_party/blink/web_tests/fast/forms/calendar-picker/calendar-picker-appearance-ar.html
+++ b/third_party/blink/web_tests/fast/forms/calendar-picker/calendar-picker-appearance-ar.html
@@ -4,7 +4,7 @@
 <script>
 window.enablePixelTesting = true;
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <script src="../../../resources/js-test.js"></script>
 <script src="../../forms/resources/picker-common.js"></script>
diff --git a/third_party/blink/web_tests/fast/forms/calendar-picker/calendar-picker-appearance-required-ar.html b/third_party/blink/web_tests/fast/forms/calendar-picker/calendar-picker-appearance-required-ar.html
index 31cd6e6..da265a2 100644
--- a/third_party/blink/web_tests/fast/forms/calendar-picker/calendar-picker-appearance-required-ar.html
+++ b/third_party/blink/web_tests/fast/forms/calendar-picker/calendar-picker-appearance-required-ar.html
@@ -4,7 +4,7 @@
 <script>
 window.enablePixelTesting = true;
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <script src="../../../resources/js-test.js"></script>
 <script src="../../forms/resources/picker-common.js"></script>
diff --git a/third_party/blink/web_tests/fast/forms/calendar-picker/calendar-picker-appearance-ru.html b/third_party/blink/web_tests/fast/forms/calendar-picker/calendar-picker-appearance-ru.html
index e3ae060a..feec114 100644
--- a/third_party/blink/web_tests/fast/forms/calendar-picker/calendar-picker-appearance-ru.html
+++ b/third_party/blink/web_tests/fast/forms/calendar-picker/calendar-picker-appearance-ru.html
@@ -4,7 +4,7 @@
 <script>
 window.enablePixelTesting = true;
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <script src="../../../resources/js-test.js"></script>
 <script src="../../forms/resources/picker-common.js"></script>
diff --git a/third_party/blink/web_tests/fast/forms/calendar-picker/date-picker-appearance-rtl.html b/third_party/blink/web_tests/fast/forms/calendar-picker/date-picker-appearance-rtl.html
index 9414b838..c1d8117e 100644
--- a/third_party/blink/web_tests/fast/forms/calendar-picker/date-picker-appearance-rtl.html
+++ b/third_party/blink/web_tests/fast/forms/calendar-picker/date-picker-appearance-rtl.html
@@ -3,7 +3,7 @@
 <script>
 testRunner.waitUntilDone();
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <script src="../resources/picker-common.js"></script>
 <input type=date id=date value="1800-05-12" lang="ar-AE">
diff --git a/third_party/blink/web_tests/fast/forms/calendar-picker/time-picker-open-to-focused-field.html b/third_party/blink/web_tests/fast/forms/calendar-picker/time-picker-open-to-focused-field.html
index 58ef14ec..cf4697fe 100644
--- a/third_party/blink/web_tests/fast/forms/calendar-picker/time-picker-open-to-focused-field.html
+++ b/third_party/blink/web_tests/fast/forms/calendar-picker/time-picker-open-to-focused-field.html
@@ -9,7 +9,7 @@
 <body>
 <script>
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 else
     debug('Require testRunner.');
 </script>
diff --git a/third_party/blink/web_tests/fast/forms/date-multiple-fields/date-multiple-fields-focus.html b/third_party/blink/web_tests/fast/forms/date-multiple-fields/date-multiple-fields-focus.html
index 34e9f61f..4ba84271 100644
--- a/third_party/blink/web_tests/fast/forms/date-multiple-fields/date-multiple-fields-focus.html
+++ b/third_party/blink/web_tests/fast/forms/date-multiple-fields/date-multiple-fields-focus.html
@@ -4,7 +4,7 @@
 <script>
 description('Check if element.focus() does not focus on disabled sub-fields.');
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 else
     debug('This test requires DRT/WRT.');
 
diff --git a/third_party/blink/web_tests/fast/forms/date-multiple-fields/date-multiple-fields-keyboard-events.html b/third_party/blink/web_tests/fast/forms/date-multiple-fields/date-multiple-fields-keyboard-events.html
index 1136882..e71fd95 100644
--- a/third_party/blink/web_tests/fast/forms/date-multiple-fields/date-multiple-fields-keyboard-events.html
+++ b/third_party/blink/web_tests/fast/forms/date-multiple-fields/date-multiple-fields-keyboard-events.html
@@ -23,7 +23,7 @@
 <script>
 description('Multiple fields UI of month input type with keyboard events');
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 var input = document.getElementById('input');
 
 function keyDown(key, modifiers)
diff --git a/third_party/blink/web_tests/fast/forms/date/date-appearance-l10n.html b/third_party/blink/web_tests/fast/forms/date/date-appearance-l10n.html
index fbe9031..df48259 100644
--- a/third_party/blink/web_tests/fast/forms/date/date-appearance-l10n.html
+++ b/third_party/blink/web_tests/fast/forms/date/date-appearance-l10n.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <script>
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <style>
 dt {
diff --git a/third_party/blink/web_tests/fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-keyboard-events.html b/third_party/blink/web_tests/fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-keyboard-events.html
index 1d7c70f5..2c728e1 100644
--- a/third_party/blink/web_tests/fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-keyboard-events.html
+++ b/third_party/blink/web_tests/fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-keyboard-events.html
@@ -23,7 +23,7 @@
 <script>
 description('Multiple fields UI of datetime-local input type with keyboard events');
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 var input = document.getElementById('input');
 
 function keyDown(key, modifiers)
diff --git a/third_party/blink/web_tests/fast/forms/datetimelocal/datetimelocal-appearance-l10n.html b/third_party/blink/web_tests/fast/forms/datetimelocal/datetimelocal-appearance-l10n.html
index 9588882..4cf71af 100644
--- a/third_party/blink/web_tests/fast/forms/datetimelocal/datetimelocal-appearance-l10n.html
+++ b/third_party/blink/web_tests/fast/forms/datetimelocal/datetimelocal-appearance-l10n.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <script>
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <style>
 dt {
diff --git a/third_party/blink/web_tests/fast/forms/month-multiple-fields/month-multiple-fields-keyboard-events.html b/third_party/blink/web_tests/fast/forms/month-multiple-fields/month-multiple-fields-keyboard-events.html
index 12dc4e67..2ea4e4b 100644
--- a/third_party/blink/web_tests/fast/forms/month-multiple-fields/month-multiple-fields-keyboard-events.html
+++ b/third_party/blink/web_tests/fast/forms/month-multiple-fields/month-multiple-fields-keyboard-events.html
@@ -23,7 +23,7 @@
 <script>
 description("Multiple fields UI of month input type with keyboard events");
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 var input = document.getElementById("input");
 
 function keyDown(key, modifiers)
diff --git a/third_party/blink/web_tests/fast/forms/month/month-appearance-l10n.html b/third_party/blink/web_tests/fast/forms/month/month-appearance-l10n.html
index 9fab28c4..656b52d24 100644
--- a/third_party/blink/web_tests/fast/forms/month/month-appearance-l10n.html
+++ b/third_party/blink/web_tests/fast/forms/month/month-appearance-l10n.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <script>
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <style>
 dt {
diff --git a/third_party/blink/web_tests/fast/forms/month/month-picker-appearance-rtl.html b/third_party/blink/web_tests/fast/forms/month/month-picker-appearance-rtl.html
index bdc3249..0eb01d3e 100644
--- a/third_party/blink/web_tests/fast/forms/month/month-picker-appearance-rtl.html
+++ b/third_party/blink/web_tests/fast/forms/month/month-picker-appearance-rtl.html
@@ -3,7 +3,7 @@
 <script>
 testRunner.waitUntilDone();
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <script src="../resources/picker-common.js"></script>
 <input type=month id=month value="2018-08" lang="ar-AE">
diff --git a/third_party/blink/web_tests/fast/forms/number/number-l10n-input.html b/third_party/blink/web_tests/fast/forms/number/number-l10n-input.html
index 1e94334..43f60779 100644
--- a/third_party/blink/web_tests/fast/forms/number/number-l10n-input.html
+++ b/third_party/blink/web_tests/fast/forms/number/number-l10n-input.html
@@ -3,7 +3,7 @@
 <script src="../../../resources/js-test.js"></script>
 <script>
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 else
     debug('Require DRT/WRT.');
 </script>
diff --git a/third_party/blink/web_tests/fast/forms/number/number-reject-invalid.html b/third_party/blink/web_tests/fast/forms/number/number-reject-invalid.html
index f8f04ea0..f687dbc 100644
--- a/third_party/blink/web_tests/fast/forms/number/number-reject-invalid.html
+++ b/third_party/blink/web_tests/fast/forms/number/number-reject-invalid.html
@@ -3,7 +3,7 @@
 <script>
 
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 else
     debug('Require testRunner.');
 </script>
diff --git a/third_party/blink/web_tests/fast/forms/number/number-validation-message.html b/third_party/blink/web_tests/fast/forms/number/number-validation-message.html
index 0edc95ab..93090371 100644
--- a/third_party/blink/web_tests/fast/forms/number/number-validation-message.html
+++ b/third_party/blink/web_tests/fast/forms/number/number-validation-message.html
@@ -6,7 +6,7 @@
 <body>
 <script>
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 description('Test for validationMessage IDL attribute for &lt;input type=number>');
 var parent = document.createElement('div');
 document.body.appendChild(parent);
diff --git a/third_party/blink/web_tests/fast/forms/number/number-validity-badinput.html b/third_party/blink/web_tests/fast/forms/number/number-validity-badinput.html
index a418c42..d4f7d4a3 100644
--- a/third_party/blink/web_tests/fast/forms/number/number-validity-badinput.html
+++ b/third_party/blink/web_tests/fast/forms/number/number-validity-badinput.html
@@ -4,7 +4,7 @@
 <script src="../../../resources/js-test.js"></script>
 <script>
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 else
     debug('Require testRunner.');
 </script>
diff --git a/third_party/blink/web_tests/fast/forms/suggestion-picker/datetimelocal-suggestion-picker-appearance-locale-hebrew.html b/third_party/blink/web_tests/fast/forms/suggestion-picker/datetimelocal-suggestion-picker-appearance-locale-hebrew.html
index 3104f9b..c562371 100644
--- a/third_party/blink/web_tests/fast/forms/suggestion-picker/datetimelocal-suggestion-picker-appearance-locale-hebrew.html
+++ b/third_party/blink/web_tests/fast/forms/suggestion-picker/datetimelocal-suggestion-picker-appearance-locale-hebrew.html
@@ -4,7 +4,7 @@
 <script>
 window.enablePixelTesting = true;
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <script src="../../../resources/js-test.js"></script>
 <script src="../../forms/resources/picker-common.js"></script>
diff --git a/third_party/blink/web_tests/fast/forms/suggestion-picker/time-suggestion-picker-appearance-locale-hebrew.html b/third_party/blink/web_tests/fast/forms/suggestion-picker/time-suggestion-picker-appearance-locale-hebrew.html
index c0fbf00..8a36d9a 100644
--- a/third_party/blink/web_tests/fast/forms/suggestion-picker/time-suggestion-picker-appearance-locale-hebrew.html
+++ b/third_party/blink/web_tests/fast/forms/suggestion-picker/time-suggestion-picker-appearance-locale-hebrew.html
@@ -4,7 +4,7 @@
 <script>
 window.enablePixelTesting = true;
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <script src="../../../resources/js-test.js"></script>
 <script src="../../forms/resources/picker-common.js"></script>
diff --git a/third_party/blink/web_tests/fast/forms/time-multiple-fields/time-multiple-fields-keyboard-events.html b/third_party/blink/web_tests/fast/forms/time-multiple-fields/time-multiple-fields-keyboard-events.html
index c9aa832..6ff560d8 100644
--- a/third_party/blink/web_tests/fast/forms/time-multiple-fields/time-multiple-fields-keyboard-events.html
+++ b/third_party/blink/web_tests/fast/forms/time-multiple-fields/time-multiple-fields-keyboard-events.html
@@ -23,7 +23,7 @@
 <script>
 description("Multiple fields UI of time input type with keyboard events");
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 var input = document.getElementById("input");
 
 function shadowPseudoIdOfFocusedSubField(host)
diff --git a/third_party/blink/web_tests/fast/forms/time-multiple-fields/time-multiple-fields-localization.html b/third_party/blink/web_tests/fast/forms/time-multiple-fields/time-multiple-fields-localization.html
index b3958df4..59d0b15 100644
--- a/third_party/blink/web_tests/fast/forms/time-multiple-fields/time-multiple-fields-localization.html
+++ b/third_party/blink/web_tests/fast/forms/time-multiple-fields/time-multiple-fields-localization.html
@@ -6,7 +6,7 @@
 <script src="../resources/common.js"></script>
 <script>
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 </head>
 <body>
diff --git a/third_party/blink/web_tests/fast/forms/time/time-picker-appearance-24-hour.html b/third_party/blink/web_tests/fast/forms/time/time-picker-appearance-24-hour.html
index 0727406..21064743 100644
--- a/third_party/blink/web_tests/fast/forms/time/time-picker-appearance-24-hour.html
+++ b/third_party/blink/web_tests/fast/forms/time/time-picker-appearance-24-hour.html
@@ -2,7 +2,7 @@
 <script>
 testRunner.waitUntilDone();
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <script src="../resources/picker-common.js"></script>
 <input type="time" id="time" value="14:15" lang="ru">
diff --git a/third_party/blink/web_tests/fast/forms/time/time-picker-appearance-ko.html b/third_party/blink/web_tests/fast/forms/time/time-picker-appearance-ko.html
index 2c8e6dd..547e96fb 100644
--- a/third_party/blink/web_tests/fast/forms/time/time-picker-appearance-ko.html
+++ b/third_party/blink/web_tests/fast/forms/time/time-picker-appearance-ko.html
@@ -2,7 +2,7 @@
 <script>
 testRunner.waitUntilDone();
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <script src="../resources/picker-common.js"></script>
 <input type="time" id="time" value="14:15" lang="ko">
diff --git a/third_party/blink/web_tests/fast/forms/time/time-picker-select-invalid-value-24-hour.html b/third_party/blink/web_tests/fast/forms/time/time-picker-select-invalid-value-24-hour.html
index 41032605..5afeea7a 100644
--- a/third_party/blink/web_tests/fast/forms/time/time-picker-select-invalid-value-24-hour.html
+++ b/third_party/blink/web_tests/fast/forms/time/time-picker-select-invalid-value-24-hour.html
@@ -8,7 +8,7 @@
 <script src="../calendar-picker/resources/calendar-picker-common.js"></script>
 <script>
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 </head>
 <body>
diff --git a/third_party/blink/web_tests/fast/forms/time/time-picker-wheel.html b/third_party/blink/web_tests/fast/forms/time/time-picker-wheel.html
index 228c148..b2073a5 100644
--- a/third_party/blink/web_tests/fast/forms/time/time-picker-wheel.html
+++ b/third_party/blink/web_tests/fast/forms/time/time-picker-wheel.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <script>
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 </script>
 <script src="../../../resources/gesture-util.js"></script>
 <script src="../../../resources/testharness.js"></script>
diff --git a/third_party/blink/web_tests/fast/forms/validationMessage.html b/third_party/blink/web_tests/fast/forms/validationMessage.html
index a9ba287..e1db3b2 100644
--- a/third_party/blink/web_tests/fast/forms/validationMessage.html
+++ b/third_party/blink/web_tests/fast/forms/validationMessage.html
@@ -10,7 +10,7 @@
 <script>
 description("Test for validationMessage DOM property.");
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 var form = document.createElement("form");
 document.body.appendChild(form);
 
diff --git a/third_party/blink/web_tests/fast/forms/week-multiple-fields/week-multiple-fields-keyboard-events.html b/third_party/blink/web_tests/fast/forms/week-multiple-fields/week-multiple-fields-keyboard-events.html
index a7f4ce40..89849a63 100644
--- a/third_party/blink/web_tests/fast/forms/week-multiple-fields/week-multiple-fields-keyboard-events.html
+++ b/third_party/blink/web_tests/fast/forms/week-multiple-fields/week-multiple-fields-keyboard-events.html
@@ -23,7 +23,7 @@
 <script>
 description('Multiple fields UI of week input type with keyboard events');
 if (window.internals)
-    internals.settings.setLangAttributeAwareFormControlUIEnabled(true);
+    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
 var input = document.getElementById('input');
 
 function keyDown(key, modifiers)
diff --git a/third_party/blink/web_tests/fast/scroll-snap/snaps-after-keyboard-scrolling-rtl.html b/third_party/blink/web_tests/fast/scroll-snap/snaps-after-keyboard-scrolling-rtl.html
index 381b0d5..ab8a610 100644
--- a/third_party/blink/web_tests/fast/scroll-snap/snaps-after-keyboard-scrolling-rtl.html
+++ b/third_party/blink/web_tests/fast/scroll-snap/snaps-after-keyboard-scrolling-rtl.html
@@ -66,6 +66,7 @@
 promise_test (async () => {
   await mouseClickOn(510, 10);
   scroller.scrollTo(-615, 0);
+  await waitForAnimationEndTimeBased(scrollLeft);
   await keyPress("ArrowRight");
   // The left border of #right is at -width.
   // The right border of #right is thus 200 - width.
@@ -76,6 +77,7 @@
 promise_test (async () => {
   await mouseClickOn(510, 10);
   scroller.scrollTo(-215, 0);
+  await waitForAnimationEndTimeBased(scrollLeft);
   await keyPress("ArrowLeft");
   // The left border of #left is at -(400 + width).
   // The right border of #left is thus -(200 + width).
diff --git a/third_party/blink/web_tests/platform/mac/fast/events/scrollbar-double-click-expected.txt b/third_party/blink/web_tests/platform/mac/fast/events/scrollbar-double-click-expected.txt
index 0c4a218..7a1ac5a6 100644
--- a/third_party/blink/web_tests/platform/mac/fast/events/scrollbar-double-click-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/fast/events/scrollbar-double-click-expected.txt
@@ -1 +1 @@
-Scroll offset is 360
+Scroll offset is 720
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/resources/prerender-state.html b/third_party/blink/web_tests/wpt_internal/prerender/resources/prerender-state.html
index ab05c47..732f8b5 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/resources/prerender-state.html
+++ b/third_party/blink/web_tests/wpt_internal/prerender/resources/prerender-state.html
@@ -63,7 +63,7 @@
     writeValueToServer(activate_key, 'did_load');
   });
 
-  document.addEventListener('prerenderingchange', (e) => {
+  addEventListener('prerenderingchange', (e) => {
     result.eventBubbles = e.bubbles;
     result.eventCancelable = e.cancelable;
 
diff --git a/third_party/closure_compiler/externs/automation.js b/third_party/closure_compiler/externs/automation.js
index 7d82132..29770c6 100644
--- a/third_party/closure_compiler/externs/automation.js
+++ b/third_party/closure_compiler/externs/automation.js
@@ -243,7 +243,6 @@
   IFRAME_PRESENTATIONAL: 'iframePresentational',
   IGNORED: 'ignored',
   IMAGE: 'image',
-  IMAGE_MAP: 'imageMap',
   IME_CANDIDATE: 'imeCandidate',
   INLINE_TEXT_BOX: 'inlineTextBox',
   INPUT_TIME: 'inputTime',
diff --git a/third_party/grpc/BUILD.gn b/third_party/grpc/BUILD.gn
index 16c9c78..25a20c2 100644
--- a/third_party/grpc/BUILD.gn
+++ b/third_party/grpc/BUILD.gn
@@ -30,11 +30,13 @@
     "HAVE_CONFIG_H",
     "PB_FIELD_16BIT",
     "GRPC_NO_XDS",
+
+    # This is defaulted to true in google3. See:
+    # go/grpc-callback-codelab#advanced-topics
+    "GRPC_CALLBACK_API_NONEXPERIMENTAL",
   ]
 
-  cflags = [
-    "-Wno-implicit-fallthrough",
-  ]
+  cflags = [ "-Wno-implicit-fallthrough" ]
 
   if (is_android) {
     libs = [ "log" ]  # For __android_log_write
@@ -87,7 +89,8 @@
     shared_library(target_name) {
       forward_variables_from(invoker, "*")
       inputs = [ "./grpc_shared_lib.map" ]
-      ldflags = [ "-Wl,--version-script=" + rebase_path("./grpc_shared_lib.map", root_build_dir) ]
+      ldflags = [ "-Wl,--version-script=" +
+                  rebase_path("./grpc_shared_lib.map", root_build_dir) ]
     }
   }
 }
@@ -97,10 +100,9 @@
     ":grpc++_cc",
     ":grpc++_repeated",
   ]
-  public_deps = [
-    ":grpc++_h",
-  ]
+  public_deps = [ ":grpc++_h" ]
 }
+
 # There are some .cc files that are in multiple places. GN doesn't like
 # that. Moving them to another target.
 source_set("grpc++_h") {
@@ -343,6 +345,7 @@
     "src/include/grpcpp/support/sync_stream.h",
     "src/include/grpcpp/support/time.h",
     "src/include/grpcpp/support/validate_service_config.h",
+    "src/include/grpcpp/xds_server_builder.h",
     "src/src/core/ext/filters/client_channel/backend_metric.h",
     "src/src/core/ext/filters/client_channel/backup_poller.h",
     "src/src/core/ext/filters/client_channel/client_channel.h",
@@ -350,6 +353,7 @@
     "src/src/core/ext/filters/client_channel/client_channel_factory.h",
     "src/src/core/ext/filters/client_channel/config_selector.h",
     "src/src/core/ext/filters/client_channel/connector.h",
+    "src/src/core/ext/filters/client_channel/dynamic_filters.h",
     "src/src/core/ext/filters/client_channel/global_subchannel_pool.h",
     "src/src/core/ext/filters/client_channel/health/health_check_client.h",
     "src/src/core/ext/filters/client_channel/http_connect_handshaker.h",
@@ -372,7 +376,6 @@
     "src/src/core/ext/filters/client_channel/resolver_factory.h",
     "src/src/core/ext/filters/client_channel/resolver_registry.h",
     "src/src/core/ext/filters/client_channel/resolver_result_parsing.h",
-    "src/src/core/ext/filters/client_channel/resolving_lb_policy.h",
     "src/src/core/ext/filters/client_channel/retry_throttle.h",
     "src/src/core/ext/filters/client_channel/server_address.h",
     "src/src/core/ext/filters/client_channel/service_config.h",
@@ -449,6 +452,7 @@
     "src/src/core/ext/upb-generated/envoy/config/route/v3/route_components.upb.h",
     "src/src/core/ext/upb-generated/envoy/config/route/v3/scoped_route.upb.h",
     "src/src/core/ext/upb-generated/envoy/config/trace/v3/http_tracer.upb.h",
+    "src/src/core/ext/upb-generated/envoy/extensions/clusters/aggregate/v3/cluster.upb.h",
     "src/src/core/ext/upb-generated/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upb.h",
     "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/cert.upb.h",
     "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/common.upb.h",
@@ -478,6 +482,12 @@
     "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/checked.upb.h",
     "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/syntax.upb.h",
     "src/src/core/ext/upb-generated/google/api/http.upb.h",
+    "src/src/core/ext/upb-generated/google/protobuf/any.upb.h",
+    "src/src/core/ext/upb-generated/google/protobuf/duration.upb.h",
+    "src/src/core/ext/upb-generated/google/protobuf/empty.upb.h",
+    "src/src/core/ext/upb-generated/google/protobuf/struct.upb.h",
+    "src/src/core/ext/upb-generated/google/protobuf/timestamp.upb.h",
+    "src/src/core/ext/upb-generated/google/protobuf/wrappers.upb.h",
     "src/src/core/ext/upb-generated/google/rpc/status.upb.h",
     "src/src/core/ext/upb-generated/src/proto/grpc/gcp/altscontext.upb.h",
     "src/src/core/ext/upb-generated/src/proto/grpc/gcp/handshaker.upb.h",
@@ -489,14 +499,14 @@
     "src/src/core/ext/upb-generated/udpa/annotations/sensitive.upb.h",
     "src/src/core/ext/upb-generated/udpa/annotations/status.upb.h",
     "src/src/core/ext/upb-generated/udpa/annotations/versioning.upb.h",
-    "src/src/core/ext/upb-generated/udpa/core/v1/authority.upb.h",
-    "src/src/core/ext/upb-generated/udpa/core/v1/collection_entry.upb.h",
-    "src/src/core/ext/upb-generated/udpa/core/v1/context_params.upb.h",
-    "src/src/core/ext/upb-generated/udpa/core/v1/resource.upb.h",
-    "src/src/core/ext/upb-generated/udpa/core/v1/resource_locator.upb.h",
-    "src/src/core/ext/upb-generated/udpa/core/v1/resource_name.upb.h",
     "src/src/core/ext/upb-generated/udpa/data/orca/v1/orca_load_report.upb.h",
     "src/src/core/ext/upb-generated/validate/validate.upb.h",
+    "src/src/core/ext/upb-generated/xds/core/v3/authority.upb.h",
+    "src/src/core/ext/upb-generated/xds/core/v3/collection_entry.upb.h",
+    "src/src/core/ext/upb-generated/xds/core/v3/context_params.upb.h",
+    "src/src/core/ext/upb-generated/xds/core/v3/resource.upb.h",
+    "src/src/core/ext/upb-generated/xds/core/v3/resource_locator.upb.h",
+    "src/src/core/ext/upb-generated/xds/core/v3/resource_name.upb.h",
     "src/src/core/ext/upbdefs-generated/envoy/annotations/deprecation.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/envoy/annotations/resource.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/envoy/config/accesslog/v3/accesslog.upbdefs.h",
@@ -528,6 +538,7 @@
     "src/src/core/ext/upbdefs-generated/envoy/config/route/v3/route_components.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/envoy/config/route/v3/scoped_route.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/http_tracer.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/envoy/extensions/transport_sockets/tls/v3/cert.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/envoy/extensions/transport_sockets/tls/v3/common.upbdefs.h",
@@ -555,19 +566,25 @@
     "src/src/core/ext/upbdefs-generated/envoy/type/v3/semantic_version.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/google/api/annotations.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/google/api/http.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/any.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/duration.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/empty.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/struct.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/timestamp.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/wrappers.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/google/rpc/status.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/udpa/annotations/migrate.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/udpa/annotations/security.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/udpa/annotations/sensitive.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/udpa/annotations/status.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/udpa/annotations/versioning.upbdefs.h",
-    "src/src/core/ext/upbdefs-generated/udpa/core/v1/authority.upbdefs.h",
-    "src/src/core/ext/upbdefs-generated/udpa/core/v1/collection_entry.upbdefs.h",
-    "src/src/core/ext/upbdefs-generated/udpa/core/v1/context_params.upbdefs.h",
-    "src/src/core/ext/upbdefs-generated/udpa/core/v1/resource.upbdefs.h",
-    "src/src/core/ext/upbdefs-generated/udpa/core/v1/resource_locator.upbdefs.h",
-    "src/src/core/ext/upbdefs-generated/udpa/core/v1/resource_name.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/validate/validate.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/authority.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/collection_entry.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/context_params.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/resource.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/resource_locator.upbdefs.h",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/resource_name.upbdefs.h",
     "src/src/core/lib/avl/avl.h",
     "src/src/core/lib/backoff/backoff.h",
     "src/src/core/lib/channel/channel_args.h",
@@ -619,7 +636,6 @@
     "src/src/core/lib/gprpp/global_config_generic.h",
     "src/src/core/lib/gprpp/host_port.h",
     "src/src/core/lib/gprpp/manual_constructor.h",
-    "src/src/core/lib/gprpp/map.h",
     "src/src/core/lib/gprpp/memory.h",
     "src/src/core/lib/gprpp/mpscq.h",
     "src/src/core/lib/gprpp/orphanable.h",
@@ -628,6 +644,7 @@
     "src/src/core/lib/gprpp/stat.h",
     "src/src/core/lib/gprpp/sync.h",
     "src/src/core/lib/gprpp/thd.h",
+    "src/src/core/lib/gprpp/time_util.h",
     "src/src/core/lib/http/format_request.h",
     "src/src/core/lib/http/httpcli.h",
     "src/src/core/lib/http/parser.h",
@@ -660,7 +677,6 @@
     "src/src/core/lib/iomgr/iomgr.h",
     "src/src/core/lib/iomgr/iomgr_custom.h",
     "src/src/core/lib/iomgr/iomgr_internal.h",
-    "src/src/core/lib/iomgr/iomgr_posix.h",
     "src/src/core/lib/iomgr/is_epollexclusive_available.h",
     "src/src/core/lib/iomgr/load_file.h",
     "src/src/core/lib/iomgr/lockfree_event.h",
@@ -714,6 +730,8 @@
     "src/src/core/lib/profiling/timers.h",
     "src/src/core/lib/security/authorization/authorization_engine.h",
     "src/src/core/lib/security/authorization/evaluate_args.h",
+
+    # "src/src/core/lib/security/authorization/matchers.h",
     "src/src/core/lib/security/authorization/mock_cel/activation.h",
     "src/src/core/lib/security/authorization/mock_cel/cel_expr_builder_factory.h",
     "src/src/core/lib/security/authorization/mock_cel/cel_expression.h",
@@ -726,6 +744,8 @@
     "src/src/core/lib/security/credentials/alts/grpc_alts_credentials_options.h",
     "src/src/core/lib/security/credentials/composite/composite_credentials.h",
     "src/src/core/lib/security/credentials/credentials.h",
+    "src/src/core/lib/security/credentials/external/aws_external_account_credentials.h",
+    "src/src/core/lib/security/credentials/external/aws_request_signer.h",
     "src/src/core/lib/security/credentials/external/external_account_credentials.h",
     "src/src/core/lib/security/credentials/external/file_external_account_credentials.h",
     "src/src/core/lib/security/credentials/external/url_external_account_credentials.h",
@@ -743,7 +763,9 @@
     "src/src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.h",
     "src/src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h",
     "src/src/core/lib/security/credentials/tls/tls_credentials.h",
-    "src/src/core/lib/security/credentials/xds/xds_credentials.h",
+    "src/src/core/lib/security/credentials/tls/tls_utils.h",
+
+    # "src/src/core/lib/security/credentials/xds/xds_credentials.h",
     "src/src/core/lib/security/security_connector/alts/alts_security_connector.h",
     "src/src/core/lib/security/security_connector/fake/fake_security_connector.h",
     "src/src/core/lib/security/security_connector/insecure/insecure_security_connector.h",
@@ -843,12 +865,8 @@
     "//third_party/protobuf:protobuf_lite",
     "//third_party/zlib",
   ]
-  public_configs = [
-    ":grpc_config",
-  ]
-  configs += [
-    ":grpc_config_private",
-  ]
+  public_configs = [ ":grpc_config" ]
+  configs += [ ":grpc_config_private" ]
   include_dirs = [
     ":cares",
     "src/third_party/address_sorting/include",
@@ -862,6 +880,9 @@
 
 source_set("grpc++_cc") {
   sources = [
+    # Disabling some default plugins.
+    # "src/src/core/plugin_registry/grpc_plugin_registry.cc",
+    "plugin_registry/grpc_plugin_registry.cc",
     "src/src/core/ext/filters/census/grpc_context.cc",
     "src/src/core/ext/filters/client_channel/backend_metric.cc",
     "src/src/core/ext/filters/client_channel/backup_poller.cc",
@@ -871,6 +892,7 @@
     "src/src/core/ext/filters/client_channel/client_channel_factory.cc",
     "src/src/core/ext/filters/client_channel/client_channel_plugin.cc",
     "src/src/core/ext/filters/client_channel/config_selector.cc",
+    "src/src/core/ext/filters/client_channel/dynamic_filters.cc",
     "src/src/core/ext/filters/client_channel/global_subchannel_pool.cc",
     "src/src/core/ext/filters/client_channel/health/health_check_client.cc",
     "src/src/core/ext/filters/client_channel/http_connect_handshaker.cc",
@@ -885,22 +907,21 @@
     "src/src/core/ext/filters/client_channel/proxy_mapper_registry.cc",
     "src/src/core/ext/filters/client_channel/resolver.cc",
     "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc",
-    "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.cc",
     "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_libuv.cc",
     "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc",
     "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc",
     "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc",
-    "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_fallback.cc",
     "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_libuv.cc",
     "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc",
     "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc",
     "src/src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc",
     "src/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc",
     "src/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc",
+
+    # "src/src/core/ext/filters/client_channel/resolver/google_c2p/google_c2p_resolver.cc",
     "src/src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc",
     "src/src/core/ext/filters/client_channel/resolver_registry.cc",
     "src/src/core/ext/filters/client_channel/resolver_result_parsing.cc",
-    "src/src/core/ext/filters/client_channel/resolving_lb_policy.cc",
     "src/src/core/ext/filters/client_channel/retry_throttle.cc",
     "src/src/core/ext/filters/client_channel/server_address.cc",
     "src/src/core/ext/filters/client_channel/service_config.cc",
@@ -1014,6 +1035,12 @@
     "src/src/core/ext/upb-generated/google/api/annotations.upb.c",
     "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/checked.upb.c",
     "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/syntax.upb.c",
+    "src/src/core/ext/upb-generated/google/protobuf/any.upb.c",
+    "src/src/core/ext/upb-generated/google/protobuf/duration.upb.c",
+    "src/src/core/ext/upb-generated/google/protobuf/empty.upb.c",
+    "src/src/core/ext/upb-generated/google/protobuf/struct.upb.c",
+    "src/src/core/ext/upb-generated/google/protobuf/timestamp.upb.c",
+    "src/src/core/ext/upb-generated/google/protobuf/wrappers.upb.c",
     "src/src/core/ext/upb-generated/google/rpc/status.upb.c",
     "src/src/core/ext/upb-generated/src/proto/grpc/gcp/altscontext.upb.c",
     "src/src/core/ext/upb-generated/src/proto/grpc/gcp/handshaker.upb.c",
@@ -1024,13 +1051,13 @@
     "src/src/core/ext/upb-generated/udpa/annotations/security.upb.c",
     "src/src/core/ext/upb-generated/udpa/annotations/sensitive.upb.c",
     "src/src/core/ext/upb-generated/udpa/annotations/versioning.upb.c",
-    "src/src/core/ext/upb-generated/udpa/core/v1/authority.upb.c",
-    "src/src/core/ext/upb-generated/udpa/core/v1/collection_entry.upb.c",
-    "src/src/core/ext/upb-generated/udpa/core/v1/context_params.upb.c",
-    "src/src/core/ext/upb-generated/udpa/core/v1/resource_locator.upb.c",
-    "src/src/core/ext/upb-generated/udpa/core/v1/resource_name.upb.c",
     "src/src/core/ext/upb-generated/udpa/data/orca/v1/orca_load_report.upb.c",
     "src/src/core/ext/upb-generated/validate/validate.upb.c",
+    "src/src/core/ext/upb-generated/xds/core/v3/authority.upb.c",
+    "src/src/core/ext/upb-generated/xds/core/v3/collection_entry.upb.c",
+    "src/src/core/ext/upb-generated/xds/core/v3/context_params.upb.c",
+    "src/src/core/ext/upb-generated/xds/core/v3/resource_locator.upb.c",
+    "src/src/core/ext/upb-generated/xds/core/v3/resource_name.upb.c",
     "src/src/core/ext/upbdefs-generated/envoy/annotations/deprecation.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/annotations/resource.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/config/accesslog/v3/accesslog.upbdefs.c",
@@ -1087,17 +1114,23 @@
     "src/src/core/ext/upbdefs-generated/envoy/type/v3/range.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/type/v3/semantic_version.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/api/annotations.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/any.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/duration.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/empty.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/struct.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/timestamp.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/wrappers.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/rpc/status.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/udpa/annotations/migrate.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/udpa/annotations/security.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/udpa/annotations/sensitive.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/udpa/annotations/versioning.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/udpa/core/v1/authority.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/udpa/core/v1/collection_entry.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/udpa/core/v1/context_params.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/udpa/core/v1/resource_locator.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/udpa/core/v1/resource_name.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/validate/validate.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/authority.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/collection_entry.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/context_params.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/resource_locator.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/resource_name.upbdefs.c",
     "src/src/core/lib/avl/avl.cc",
     "src/src/core/lib/backoff/backoff.cc",
     "src/src/core/lib/channel/channel_args.cc",
@@ -1151,6 +1184,7 @@
     "src/src/core/lib/gpr/tmpfile_msys.cc",
     "src/src/core/lib/gpr/tmpfile_posix.cc",
     "src/src/core/lib/gpr/tmpfile_windows.cc",
+
     # gRPC memcpy wrapping logic isn't useful here.
     # See https://crbug.com/661171
     # "src/src/core/lib/gpr/wrap_memcpy.cc",
@@ -1164,6 +1198,7 @@
     "src/src/core/lib/gprpp/stat_windows.cc",
     "src/src/core/lib/gprpp/thd_posix.cc",
     "src/src/core/lib/gprpp/thd_windows.cc",
+    "src/src/core/lib/gprpp/time_util.cc",
     "src/src/core/lib/http/format_request.cc",
     "src/src/core/lib/http/httpcli.cc",
     "src/src/core/lib/http/httpcli_security_connector.cc",
@@ -1271,6 +1306,8 @@
     "src/src/core/lib/profiling/stap_timers.cc",
     "src/src/core/lib/security/authorization/authorization_engine.cc",
     "src/src/core/lib/security/authorization/evaluate_args.cc",
+
+    # "src/src/core/lib/security/authorization/matchers.cc",
     "src/src/core/lib/security/context/security_context.cc",
     "src/src/core/lib/security/credentials/alts/alts_credentials.cc",
     "src/src/core/lib/security/credentials/alts/check_gcp_environment.cc",
@@ -1283,6 +1320,8 @@
     "src/src/core/lib/security/credentials/composite/composite_credentials.cc",
     "src/src/core/lib/security/credentials/credentials.cc",
     "src/src/core/lib/security/credentials/credentials_metadata.cc",
+    "src/src/core/lib/security/credentials/external/aws_external_account_credentials.cc",
+    "src/src/core/lib/security/credentials/external/aws_request_signer.cc",
     "src/src/core/lib/security/credentials/external/external_account_credentials.cc",
     "src/src/core/lib/security/credentials/external/file_external_account_credentials.cc",
     "src/src/core/lib/security/credentials/external/url_external_account_credentials.cc",
@@ -1302,7 +1341,9 @@
     "src/src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.cc",
     "src/src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc",
     "src/src/core/lib/security/credentials/tls/tls_credentials.cc",
-    "src/src/core/lib/security/credentials/xds/xds_credentials.cc",
+    "src/src/core/lib/security/credentials/tls/tls_utils.cc",
+
+    # "src/src/core/lib/security/credentials/xds/xds_credentials.cc",
     "src/src/core/lib/security/security_connector/alts/alts_security_connector.cc",
     "src/src/core/lib/security/security_connector/fake/fake_security_connector.cc",
     "src/src/core/lib/security/security_connector/insecure/insecure_security_connector.cc",
@@ -1360,9 +1401,6 @@
     "src/src/core/lib/transport/transport.cc",
     "src/src/core/lib/transport/transport_op_string.cc",
     "src/src/core/lib/uri/uri_parser.cc",
-    # Disabling some default plugins.
-    # "src/src/core/plugin_registry/grpc_plugin_registry.cc",
-    "plugin_registry/grpc_plugin_registry.cc",
     "src/src/core/tsi/alts/crypt/aes_gcm.cc",
     "src/src/core/tsi/alts/crypt/gsec.cc",
     "src/src/core/tsi/alts/frame_protector/alts_counter.cc",
@@ -1432,6 +1470,8 @@
     "src/src/cpp/server/server_context.cc",
     "src/src/cpp/server/server_credentials.cc",
     "src/src/cpp/server/server_posix.cc",
+
+    # "src/src/cpp/server/xds_server_credentials.cc",
     "src/src/cpp/thread_manager/thread_manager.cc",
     "src/src/cpp/util/byte_buffer_cc.cc",
     "src/src/cpp/util/status.cc",
@@ -1449,12 +1489,8 @@
     "//third_party/protobuf:protobuf_lite",
     "//third_party/zlib",
   ]
-  public_configs = [
-    ":grpc_config",
-  ]
-  configs += [
-    ":grpc_config_private",
-  ]
+  public_configs = [ ":grpc_config" ]
+  configs += [ ":grpc_config_private" ]
   include_dirs = [
     ":cares",
     "src/third_party/address_sorting/include",
@@ -1468,17 +1504,20 @@
 
 source_set("grpc++_repeated") {
   sources = [
+    "src/src/core/ext/upb-generated/envoy/extensions/clusters/aggregate/v3/cluster.upb.c",
     "src/src/core/ext/upb-generated/envoy/type/metadata/v3/metadata.upb.c",
     "src/src/core/ext/upb-generated/google/api/http.upb.c",
     "src/src/core/ext/upb-generated/udpa/annotations/status.upb.c",
-    "src/src/core/ext/upb-generated/udpa/core/v1/resource.upb.c",
+    "src/src/core/ext/upb-generated/xds/core/v3/resource.upb.c",
+    "src/src/core/ext/upbdefs-generated/envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/type/metadata/v3/metadata.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/api/http.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/udpa/annotations/status.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/udpa/core/v1/resource.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/resource.upbdefs.c",
     "src/src/core/lib/security/util/json_util.cc",
     "src/src/cpp/client/insecure_credentials.cc",
-    "src/src/cpp/client/xds_credentials.cc",
+
+    # "src/src/cpp/client/xds_credentials.cc",
   ]
 
   deps = [
@@ -1491,12 +1530,8 @@
     "//third_party/protobuf:protobuf_lite",
     "//third_party/zlib",
   ]
-  public_configs = [
-    ":grpc_config",
-  ]
-  configs += [
-    ":grpc_config_private",
-  ]
+  public_configs = [ ":grpc_config" ]
+  configs += [ ":grpc_config_private" ]
   visibility = [ "./*" ]
   if (!is_win) {
     configs -= [ "//build/config/gcc:symbol_visibility_hidden" ]
@@ -1512,15 +1547,9 @@
     "src/third_party/address_sorting/address_sorting_windows.c",
     "src/third_party/address_sorting/include/address_sorting/address_sorting.h",
   ]
-  public_configs = [
-    ":grpc_config",
-  ]
-  configs += [
-    ":grpc_config_private",
-  ]
-  include_dirs = [
-    "src/third_party/address_sorting/include",
-  ]
+  public_configs = [ ":grpc_config" ]
+  configs += [ ":grpc_config_private" ]
+  include_dirs = [ "src/third_party/address_sorting/include" ]
   visibility = [ "./*" ]
   if (!is_win) {
     configs -= [ "//build/config/gcc:symbol_visibility_hidden" ]
@@ -1530,7 +1559,7 @@
 
 # Only compile the plugin for the host architecture.
 if (current_toolchain == host_toolchain) {
-    source_set("grpc_plugin_support") {
+  source_set("grpc_plugin_support") {
     sources = [
       "src/include/grpc++/impl/codegen/config_protobuf.h",
       "src/include/grpcpp/impl/codegen/config_protobuf.h",
@@ -1561,58 +1590,34 @@
       "src/src/compiler/schema_interface.h",
     ]
 
-    deps = [
-      "//third_party/protobuf:protoc_lib",
-    ]
-    public_configs = [
-      ":grpc_config",
-    ]
-    configs += [
-      ":grpc_config_private",
-    ]
+    deps = [ "//third_party/protobuf:protoc_lib" ]
+    public_configs = [ ":grpc_config" ]
+    configs += [ ":grpc_config_private" ]
   }
-
 }
 source_set("upb") {
   sources = [
-    "src/src/core/ext/upb-generated/google/protobuf/any.upb.c",
-    "src/src/core/ext/upb-generated/google/protobuf/any.upb.h",
     "src/src/core/ext/upb-generated/google/protobuf/descriptor.upb.c",
     "src/src/core/ext/upb-generated/google/protobuf/descriptor.upb.h",
-    "src/src/core/ext/upb-generated/google/protobuf/duration.upb.c",
-    "src/src/core/ext/upb-generated/google/protobuf/duration.upb.h",
-    "src/src/core/ext/upb-generated/google/protobuf/empty.upb.c",
-    "src/src/core/ext/upb-generated/google/protobuf/empty.upb.h",
-    "src/src/core/ext/upb-generated/google/protobuf/struct.upb.c",
-    "src/src/core/ext/upb-generated/google/protobuf/struct.upb.h",
-    "src/src/core/ext/upb-generated/google/protobuf/timestamp.upb.c",
-    "src/src/core/ext/upb-generated/google/protobuf/timestamp.upb.h",
-    "src/src/core/ext/upb-generated/google/protobuf/wrappers.upb.c",
-    "src/src/core/ext/upb-generated/google/protobuf/wrappers.upb.h",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/any.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/any.upbdefs.h",
     "src/src/core/ext/upbdefs-generated/google/protobuf/descriptor.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/protobuf/descriptor.upbdefs.h",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/duration.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/duration.upbdefs.h",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/empty.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/empty.upbdefs.h",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/struct.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/struct.upbdefs.h",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/timestamp.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/timestamp.upbdefs.h",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/wrappers.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/wrappers.upbdefs.h",
+    "src/third_party/upb/third_party/wyhash/wyhash.h",
     "src/third_party/upb/upb/decode.c",
     "src/third_party/upb/upb/decode.h",
+    "src/third_party/upb/upb/decode.int.h",
+    "src/third_party/upb/upb/decode_fast.c",
+    "src/third_party/upb/upb/decode_fast.h",
     "src/third_party/upb/upb/def.c",
     "src/third_party/upb/upb/def.h",
     "src/third_party/upb/upb/def.hpp",
     "src/third_party/upb/upb/encode.c",
     "src/third_party/upb/upb/encode.h",
+    "src/third_party/upb/upb/json_decode.c",
+    "src/third_party/upb/upb/json_decode.h",
+    "src/third_party/upb/upb/json_encode.c",
+    "src/third_party/upb/upb/json_encode.h",
     "src/third_party/upb/upb/msg.c",
     "src/third_party/upb/upb/msg.h",
-    "src/third_party/upb/upb/port.c",
     "src/third_party/upb/upb/port_def.inc",
     "src/third_party/upb/upb/port_undef.inc",
     "src/third_party/upb/upb/reflection.c",
@@ -1624,13 +1629,10 @@
     "src/third_party/upb/upb/upb.c",
     "src/third_party/upb/upb/upb.h",
     "src/third_party/upb/upb/upb.hpp",
+    "src/third_party/upb/upb/upb.int.h",
   ]
-  public_configs = [
-    ":grpc_config",
-  ]
-  configs += [
-    ":grpc_config_private",
-  ]
+  public_configs = [ ":grpc_config" ]
+  configs += [ ":grpc_config_private" ]
   visibility = [ "./*" ]
   if (!is_win) {
     configs -= [ "//build/config/gcc:symbol_visibility_hidden" ]
@@ -1641,16 +1643,12 @@
 # Only compile the plugin for the host architecture.
 if (current_toolchain == host_toolchain) {
   executable("grpc_cpp_plugin") {
-    sources = [
-      "src/src/compiler/cpp_plugin.cc",
-    ]
+    sources = [ "src/src/compiler/cpp_plugin.cc" ]
     deps = [
       ":grpc_plugin_support",
       "//third_party/protobuf:protoc_lib",
     ]
-    configs += [
-      "//third_party/protobuf:protobuf_config",
-    ]
+    configs += [ "//third_party/protobuf:protobuf_config" ]
     public_configs = [ ":grpc_config" ]
   }
 }
@@ -1663,9 +1661,7 @@
 }
 
 source_set("cares") {
-  sources = [
-    "src/third_party/cares/ares_build.h",
-  ]
+  sources = [ "src/third_party/cares/ares_build.h" ]
 
   if (enable_grpc_ares) {
     sources += [
@@ -1757,11 +1753,10 @@
   } else {
     sources += [ "src/third_party/cares/config_linux/ares_config.h" ]
   }
-  deps = [
-    "//third_party/boringssl",
-  ]
+  deps = [ "//third_party/boringssl" ]
 
   public_configs = [ ":grpc_config" ]
 }
 
-group("fuzzers") {}
+group("fuzzers") {
+}
diff --git a/third_party/grpc/README.chromium b/third_party/grpc/README.chromium
index 095dd2b..2180f32 100644
--- a/third_party/grpc/README.chromium
+++ b/third_party/grpc/README.chromium
@@ -1,21 +1,26 @@
 Name: grpc
 URL: https://github.com/grpc/grpc
 License: Apache 2.0
-Version: v1.33.0+
-Revision: 4ac9c6f755463a2321f84b0cb2d631e1828faedb
+Version: v1.36.1+
+Revision: 3ca079faadfcc1f111b6c9a3f3fb10f4b5c794ea
 Security Critical: yes
 
 Steps to upgrade to a new version of GRPC:
-1. Merge origin/grpc/master into eureka/master branch.
+1. Update ../../DEPS to pull origin/grpc/master.
+   NOTE: Tagged "official releases" do not work on the buildbots. The master
+   branch must be used.
 2. Update revision and version information in this file.
-4. Checkout GRPC submodules with: git submodule update --init
-5. Copy template/BUILD.chromium.gn.template to src/templates
-6. Rebuild BUILD.gn by running tools/buildgen/generate_projects.sh
-    (make sure mako_templates python module is installed in your system)
-   and then running gn format BUILD.gn. This will use the template in
-   templates/BUILD.chromium.gn.template to generate BUILD.chromium.gn file.
-4. Do:
-  mv third_party/grpc/src/BUILD.chromium.gn BUILD.gn
-6. Update plugin_register. Ensure xds_resolver and all lb_policies are
-disabled except first_pick.
-5. Run 'gn format --in-place BUILD.gn'
+3. Checkout GRPC submodules by running from the src/ directory:
+   git submodule update --init
+4. Copy template/BUILD.chromium.gn.template to src/templates.
+5. Rebuild BUILD.gn by running from the src/ directory:
+   tools/buildgen/generate_projects.sh
+    (make sure mako_templates python module is installed in your system using
+     command apt-get install python3-mako)
+   This will use the template in templates/BUILD.chromium.gn.template to
+   generate src/BUILD.chromium.gn file.
+6. Run: mv third_party/grpc/src/BUILD.chromium.gn third_party/grpc/BUILD.gn
+7. If the new gRPC version adds new dependencies, this may result in linker
+   errors when building due to some of the gRPC Plugins that have been manually
+   disabled. Manually remove these from the BUILD.gn file.
+8. Run: gn format --in-place BUILD.gn
diff --git a/third_party/grpc/template/BUILD.chromium.gn.template b/third_party/grpc/template/BUILD.chromium.gn.template
index 90015a8..9c66df9 100644
--- a/third_party/grpc/template/BUILD.chromium.gn.template
+++ b/third_party/grpc/template/BUILD.chromium.gn.template
@@ -35,6 +35,10 @@
       "HAVE_CONFIG_H",
       "PB_FIELD_16BIT",
       "GRPC_NO_XDS",
+
+      # This is defaulted to true in google3. See:
+      # go/grpc-callback-codelab#advanced-topics
+      "GRPC_CALLBACK_API_NONEXPERIMENTAL",
     ]
 
     cflags = [
diff --git a/third_party/libaddressinput/BUILD.gn b/third_party/libaddressinput/BUILD.gn
index 803c342a..54c98d6 100644
--- a/third_party/libaddressinput/BUILD.gn
+++ b/third_party/libaddressinput/BUILD.gn
@@ -29,7 +29,7 @@
     "messages.h",
     "en_messages.cc",
   ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "address_input_strings_$locale.pak" ]
   }
 
diff --git a/third_party/libdrm/BUILD.gn b/third_party/libdrm/BUILD.gn
index 1da5e188..8c1054b 100644
--- a/third_party/libdrm/BUILD.gn
+++ b/third_party/libdrm/BUILD.gn
@@ -78,5 +78,7 @@
     "src/tests/modetest",
   ]
 
+  configs -= [ "//build/config/compiler:chromium_code" ]
+  configs += [ "//build/config/compiler:no_chromium_code" ]
   deps = [ ":libdrm" ]
 }
diff --git a/third_party/libvpx/BUILD.gn b/third_party/libvpx/BUILD.gn
index 4e5ff98..b1eb76a 100644
--- a/third_party/libvpx/BUILD.gn
+++ b/third_party/libvpx/BUILD.gn
@@ -307,7 +307,7 @@
 }
 
 static_library("libvpx") {
-  if (!is_debug && is_win) {
+  if (!is_debug && (is_win || is_chromeos)) {
     configs -= [ "//build/config/compiler:default_optimization" ]
     configs += [ "//build/config/compiler:optimize_max" ]
   }
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index ce39da2..ebe07e8 100644
--- a/third_party/nearby/README.chromium
+++ b/third_party/nearby/README.chromium
@@ -1,7 +1,7 @@
 Name: Nearby Connections Library
 Short Name: Nearby
 URL: https://github.com/google/nearby-connections
-Version: bf22c7886559ba24d60c1ca35a13702f3c2c3686
+Version: 8fafd3ef09818e1983945aa8be9663713c79c142
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/node/README.chromium b/third_party/node/README.chromium
index 2a489ce..bbc21afb 100644
--- a/third_party/node/README.chromium
+++ b/third_party/node/README.chromium
@@ -38,7 +38,7 @@
 Name: Typescript Compiler
 Short Name: typescript
 URL: https://www.npmjs.com/package/typescript
-Version: 3.5.3
+Version: 4.2.3
 License: Apache 2.0
 Security Critical: No. The compiler is not shipped with Chrome but code compiled using it is.
 
diff --git a/third_party/node/node_modules.tar.gz.sha1 b/third_party/node/node_modules.tar.gz.sha1
index 1aba3ec9..442ae83a 100644
--- a/third_party/node/node_modules.tar.gz.sha1
+++ b/third_party/node/node_modules.tar.gz.sha1
@@ -1 +1 @@
-f44430862b93f5fa7edc6a27e175c4defa0140bc
+9c21d5c30210bf1e4a2391b0292ad2c7bad3a4de
diff --git a/third_party/node/package-lock.json b/third_party/node/package-lock.json
index d678c931..d15c634 100644
--- a/third_party/node/package-lock.json
+++ b/third_party/node/package-lock.json
@@ -2419,9 +2419,9 @@
       "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="
     },
     "typescript": {
-      "version": "3.5.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz",
-      "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g=="
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
+      "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw=="
     },
     "typical": {
       "version": "2.6.1",
diff --git a/third_party/node/package.json b/third_party/node/package.json
index 934b2a9..9769f41 100644
--- a/third_party/node/package.json
+++ b/third_party/node/package.json
@@ -10,6 +10,6 @@
     "polymer-css-build": "0.7.0",
     "terser": "5.3.3",
     "svgo": "1.2.0",
-    "typescript": "3.5.3"
+    "typescript": "4.2.3"
   }
 }
diff --git a/tools/accessibility/inspect/README.md b/tools/accessibility/inspect/README.md
index 759ae36..eff9acb 100644
--- a/tools/accessibility/inspect/README.md
+++ b/tools/accessibility/inspect/README.md
@@ -2,47 +2,71 @@
 
 These cli tools are designed to help to debug accessibility problems in applications, primarily in web browsers.
 
-`ax_dump_tree` to inspect accessibility tree of application
-`ax_dump_events` to watch accessibility events fired by application
+* `ax_dump_tree` to inspect accessibility tree of application
+* `ax_dump_events` to watch accessibility events fired by application
 
-Note: if you are on Windows, see convenience scripts section, it makes the tools easier to use on Windows.
+## Prerequisites
 
-## Build
-
-`autoninja -C out/Default ax_dump_tree ax_dump_events`
-
-The command generates `ax_dump_tree`  and `ax_dump_events` executables  in `out/Default` directory.
-
-## Prerequesties
+You may be required to enable accessibility on your system. Depending on
+the application, you may also be required to run an assistive technology
+like a screen reader or use an application-specific runtime flag or flip
+a preference on in order to activate accessibility application-wide.
 
 ### Mac
 
 1) Turn on Accessibility for Terminal in Security & Privacy System Preferences.
 
-2) Some applications keep accessibility inactive, which prevents them to generate accessible trees or emit events. Thus either:
-* start VoiceOver (`CMD+F5`) or
-* use application specific runtime flags
-** Chromium: `Chromium.app/Contents/MacOS/Chromium --force-renderer-accessibility`
+2) Either
+* Start VoiceOver (`CMD+F5`) or
+* Use application specific flag/preference
+  * Chrome/Chromium: use `--force-renderer-accessibility` runtime flag
+
+For example, to enable accessibility in Chromium run:
+```Chromium.app/Contents/MacOS/Chromium --force-renderer-accessibility```
+
+### Linux
+
+Either
+* Start Orca or
+* Use application specific flag/preference
+  * Chrome/Chromium: use `--force-renderer-accessibility` runtime flag
+
+### Windows
+
+Either
+* Start Narrator/NVDA/JAWS or
+* Use application specific flag/preference
+  * Chrome/Chromium: use `--force-renderer-accessibility` runtime flag
 
 ## ax_dump_tree
 
 Helps to inspect accessibility trees of applications. Trees are dumped into console.
 
-### Run
-
 To dump an accessible tree, run:
 `ax_dump_tree <options>`
 
-At your convenience the number of pre-defined application selectors are available:
-`--chrome` for Chrome browser
-`--chromium` for Chromium browser
-`--firefox` for Firefox browser
-`--safari` for Safari browser
+## ax_dump_events
 
-`--active-tab` to dump a tree of active tab of selected browser.
+The tool logs into console accessibility events fired by application.
+
+To watch accessibility events, run:
+`ax_dump_events <options>`
+
+## Options
+
+At your convenience the number of pre-defined application selectors are available:
+* `--chrome` for Chrome browser
+* `--chromium` for Chromium browser
+* `--firefox` for Firefox browser
+* `--edge` for Edge browser (Windows only)
+* `--safari` for Safari browser (Mac only)
+
+`--active-tab` to dump a tree of active tab of a selected browser.
 
 You can also specify an application by its title:
 `ax_dump_tree --pattern=title`
+The pattern string can contain wildcards like * and ?. Note, you can use
+``--pattern`` selector in conjunction with pre-defined selectors.
 
 Alternatively you can dump a tree by HWND on Windows:
 `--pid=HWND`
@@ -52,26 +76,26 @@
 `--pid=process_id`
 
 Other options:
-`--filters=absolute_path_to_filters.txt` to filter properties, use where the filters text file has a series of `@ALLOW` and/or `@DENY` lines. See example-tree-filters.txt in tools/accessibility/inspect.
+`--filters=absolute_path_to_filters.txt` to filter properties, use where
+the filters text file has a series of `@ALLOW` and/or `@DENY` lines. See
+example-tree-filters.txt in tools/accessibility/inspect.
+
 `--help` for help
 
-## ax_dump_events
+## Examples
 
-The tool logs into console accessibility events fired by application.
+To dump Edge accessible tree on Windows:
+``out\Default\ax_dump_tree --edge``
 
-To watch accessibility events, run:
-`ax_dump_events <options>`
+To dump an accessible tree on Mac for a Firefox window having title containing ``mozilla``:
+``out/Default/ax_dump_tree --firefox --pattern=*mozilla*``
 
-Use these pre-defined application selectors to indicate application:
-`--chrome` for Chrome browser
-`--chromium` for Chromium browser
-`--firefox` for Firefox browser
-`--safari` for Safari browser
+To watch Chromium accessibility events on Linux:
+``out/Default/ax_dump_events --chromium``
 
-`--active-tab` to scope events to an active tab of selected browser.
+## Build
 
-You can also specify application by its title:
-`--pattern=title`
+`autoninja -C out/Default ax_dump_tree ax_dump_events`
 
-Or by application PID:
-`--pid=process_id`
+The command generates `ax_dump_tree`  and `ax_dump_events` executables  in
+`out/Default` directory.
diff --git a/tools/clang/scripts/build.py b/tools/clang/scripts/build.py
index ba3cc0f6..95f855b 100755
--- a/tools/clang/scripts/build.py
+++ b/tools/clang/scripts/build.py
@@ -582,6 +582,8 @@
         '-DLLVM_LOCAL_RPATH=' + os.path.join(args.gcc_toolchain, 'lib64')
     ]
 
+  if sys.platform.startswith('linux'):
+    base_cmake_args.append('-DCLANG_DEFAULT_RTLIB=libgcc')
 
   if sys.platform == 'darwin':
     # For libc++, we only want the headers.
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index 89138ff..cecbbaf 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -326,7 +326,8 @@
     "META": {"sizes": {"includes": [10],}},
     "includes": [2270],
   },
-  "content/browser/webrtc/resources/resources.grd": {
+  "<(SHARED_INTERMEDIATE_DIR)/content/browser/webrtc/resources/resources.grd": {
+    "META": {"sizes": {"includes": [20],}},
     "includes": [2280],
   },
   "content/dev_ui_content_resources.grd": {
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index be3304d..e34bba4 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -123,7 +123,7 @@
       'android-nougat-arm64-rel': 'android_release_bot_minimal_symbols_arm64_fastbuild_webview_google',
       # TODO(crbug/1182468) Remove android coverage bots after coverage is
       # running on CQ.
-      'android-pie-arm64-coverage-experimental-rel': 'android_release_bot_arm64_webview_google_native_coverage',
+      'android-pie-arm64-coverage-experimental-rel': 'android_release_bot_arm64_webview_google_expectations_native_coverage',
       'android-pie-arm64-rel': 'android_release_bot_minimal_symbols_arm64_webview_google',
       'android-pie-x86-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_google',
       'android-weblayer-x86-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_google',
@@ -789,7 +789,7 @@
       # TODO(crbug/1182468) Remove android coverage bots after coverage is
       # running on CQ.
       'android-pie-arm64-coverage-rel': 'android_release_trybot_arm64_webview_google_native_coverage',
-      'android-pie-arm64-coverage-experimental-rel': 'android_release_trybot_arm64_webview_google_native_coverage',
+      'android-pie-arm64-coverage-experimental-rel': 'android_release_trybot_arm64_webview_google_expectations_native_coverage',
       'android-pie-arm64-rel': 'android_release_trybot_arm64_webview_google_expectations',
       'android-pie-arm64-wpt-rel-non-cq': 'android_release_trybot_arm64_webview_google',
       'android-pie-x86-rel': 'android_release_trybot_x86_fastbuild_webview_google',
@@ -905,7 +905,7 @@
       # and two kevin bots when the PFQ has it enabled.
       'chromeos-amd64-generic-cfi-thin-lto-rel': 'chromeos_amd64-generic_cfi_thin_lto',
       'chromeos-amd64-generic-dbg': 'chromeos_amd64-generic_dbg',
-      'chromeos-amd64-generic-rel': 'chromeos_amd64-generic_use_fake_dbus_clients',
+      'chromeos-amd64-generic-rel': 'chromeos_amd64-generic_use_fake_dbus_clients_dchecks',
       'chromeos-arm-generic-dbg': 'chromeos_arm-generic_dbg',
       'chromeos-arm-generic-rel': 'chromeos_arm-generic_dcheck_always_on',
       'chromeos-kevin-compile-rel': 'chromeos_kevin',
@@ -1432,17 +1432,17 @@
 
     # TODO(crbug/1182468) Remove these android coverage expectations after
     # existing CQ bot is running them.
-    'android_release_bot_arm64_webview_google_native_coverage': [
+    'android_release_bot_arm64_webview_google_expectations_native_coverage': [
       'android', 'release_bot', 'arm64', 'strip_debug_info',
-      'webview_google',
+      'webview_google', 'fail_on_android_expectations',
       'use_clang_coverage', 'partial_code_coverage_instrumentation',
     ],
 
     # TODO(crbug/1182468) Remove these android coverage expectations after
     # existing CQ bot is running them.
-    'android_release_trybot_arm64_webview_google_native_coverage': [
+    'android_release_trybot_arm64_webview_google_expectations_native_coverage': [
       'android', 'release_trybot', 'arm64', 'strip_debug_info',
-      'webview_google',
+      'webview_google', 'fail_on_android_expectations',
       'use_clang_coverage', 'partial_code_coverage_instrumentation',
     ],
 
@@ -1711,6 +1711,10 @@
       'chromeos_amd64-generic', 'use_fake_dbus_clients',
     ],
 
+    'chromeos_amd64-generic_use_fake_dbus_clients_dchecks': [
+      'chromeos_amd64-generic', 'use_fake_dbus_clients', 'dcheck_always_on',
+    ],
+
     'chromeos_arm-generic': [
       'chromeos_device', 'arm-generic',
     ],
diff --git a/tools/mb/mb_config_expectations/chromium.android.json b/tools/mb/mb_config_expectations/chromium.android.json
index e192be4..3dad586 100644
--- a/tools/mb/mb_config_expectations/chromium.android.json
+++ b/tools/mb/mb_config_expectations/chromium.android.json
@@ -473,6 +473,7 @@
   "android-pie-arm64-coverage-experimental-rel": {
     "gn_args": {
       "coverage_instrumentation_input_file": "//.code-coverage/files_to_instrument.txt",
+      "fail_on_android_expectations": true,
       "ffmpeg_branding": "Chrome",
       "is_component_build": false,
       "is_debug": false,
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.android.json b/tools/mb/mb_config_expectations/tryserver.chromium.android.json
index 632209e..d5e0c9f 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.android.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.android.json
@@ -298,6 +298,7 @@
     "gn_args": {
       "coverage_instrumentation_input_file": "//.code-coverage/files_to_instrument.txt",
       "dcheck_always_on": true,
+      "fail_on_android_expectations": true,
       "ffmpeg_branding": "Chrome",
       "is_component_build": false,
       "is_debug": false,
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.chromiumos.json b/tools/mb/mb_config_expectations/tryserver.chromium.chromiumos.json
index d96d63f2..c198318 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.chromiumos.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.chromiumos.json
@@ -22,6 +22,7 @@
   "chromeos-amd64-generic-rel": {
     "args_file": "//build/args/chromeos/amd64-generic.gni",
     "gn_args": {
+      "dcheck_always_on": true,
       "is_chromeos_device": true,
       "ozone_platform_headless": true,
       "use_goma": true,
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 0d8e07c..84172c12 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -14061,6 +14061,7 @@
 </action>
 
 <action name="MobileMenuBackward">
+  <obsolete>Deprecated as of 3/2021</obsolete>
   <owner>gangwu@chromium.org</owner>
   <description>User pressed the back icon in the app menu.</description>
 </action>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 0f51677..340a3d5 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -22674,6 +22674,19 @@
   <int value="902" label="SERVICE_POLICY_NOT_FOUND"/>
 </enum>
 
+<enum name="EnterpriseDlpPolicyRestriction">
+  <summary>
+    Type of restriction enforced by Data Leak Prevention policy.
+  </summary>
+  <int value="0" label="Unknown"/>
+  <int value="1" label="Clipboard"/>
+  <int value="2" label="Screenshot"/>
+  <int value="3" label="Printing"/>
+  <int value="4" label="Privacy Screen"/>
+  <int value="5" label="Screenshare"/>
+  <int value="6" label="Files"/>
+</enum>
+
 <enum name="EnterpriseDMServerRequestSuccess">
   <summary>
     Number of DeviceManagementServer request retries as defined in
@@ -24012,6 +24025,9 @@
 </enum>
 
 <enum name="EnterpriseRetrievePolicyResponseType">
+  <obsolete>
+    Removed in M91 since the data is no longer monitored.
+  </obsolete>
   <summary>
     Status codes produced by SessionManagerClient for policy retrieval requests.
     Corresponds to RetrievePolicyResponseType in
@@ -43316,6 +43332,9 @@
 </enum>
 
 <enum name="LockToSingleUserResult">
+  <obsolete>
+    Removed in M91, enough data has been gathered.
+  </obsolete>
   <int value="0" label="Success"/>
   <int value="1" label="No response"/>
   <int value="2" label="Failed to lock"/>
@@ -45151,6 +45170,7 @@
   <int value="-561194974" label="AutofillExpandedPopupViews:enabled"/>
   <int value="-560551550" label="use-memory-pressure-chromeos"/>
   <int value="-560114351" label="OfflinePagesRenovations:disabled"/>
+  <int value="-558488712" label="PreemtiveLinkToTextGeneration:disabled"/>
   <int value="-558471324" label="PreviewsCoinFlipHoldback_UKMOnly:disabled"/>
   <int value="-557742250" label="ContentSuggestionsCategories:disabled"/>
   <int value="-556218705" label="SlowDCTimerInterruptsWin:enabled"/>
@@ -46565,6 +46585,7 @@
   <int value="740619684" label="ExtensionsToolbarMenu:enabled"/>
   <int value="741219332" label="ShareByDefaultInCCT:enabled"/>
   <int value="742083923" label="MimeHandlerViewInCrossProcessFrame:disabled"/>
+  <int value="743714331" label="HelpAppLauncherSearch:enabled"/>
   <int value="744342941" label="SafetyCheckChromeCleanerChild:enabled"/>
   <int value="745541471" label="PaintHolding:disabled"/>
   <int value="745783589" label="translate-force-trigger-on-english"/>
@@ -47018,7 +47039,6 @@
   <int value="1177628103" label="GaiaActionButtons:disabled"/>
   <int value="1179013979"
       label="OmniboxUIExperimentMaxAutocompleteMatches:enabled"/>
-  <int value="1179407596" label="PreemptiveLinkToTextGeneration:enabled"/>
   <int value="1179936481" label="enable-android-pay-integration-v1"/>
   <int value="1180722846" label="OculusVR:disabled"/>
   <int value="1181056275" label="enable-cloud-backup"/>
@@ -47418,6 +47438,7 @@
   <int value="1530182188" label="PdfHonorJsContentSettings:enabled"/>
   <int value="1531164533" label="SyncPseudoUSSFavicons:disabled"/>
   <int value="1531741496" label="DragFromShelfToHomeOrOverview:disabled"/>
+  <int value="1532505461" label="HelpAppLauncherSearch:disabled"/>
   <int value="1533004890" label="CrostiniEnableDlc:enabled"/>
   <int value="1534222388" label="EnableEphemeralFlashPermission:enabled"/>
   <int value="1534386287" label="SendTabToSelfWhenSignedIn:enabled"/>
@@ -47484,6 +47505,7 @@
   <int value="1601231448" label="VirtualKeyboardMultipaste:disabled"/>
   <int value="1601582484" label="enable-crash-reporter-for-testing"/>
   <int value="1602627012" label="OverrideSitePrefsForHrefTranslate:enabled"/>
+  <int value="1602791752" label="PreemtiveLinkToTextGeneration:enabled"/>
   <int value="1602869271" label="ChromeShareScreenshot:disabled"/>
   <int value="1603578716" label="CaptionSettings:disabled"/>
   <int value="1604893983" label="VizForWebView:disabled"/>
@@ -47690,7 +47712,6 @@
   <int value="1798347197"
       label="ContextualSuggestionsIPHReverseScroll:disabled"/>
   <int value="1799521026" label="LegacyTLSEnforced:disabled"/>
-  <int value="1799526742" label="PreemptiveLinkToTextGeneration:disabled"/>
   <int value="1801585504" label="ContextMenuShopWithGoogleLens:enabled"/>
   <int value="1802874714" label="QueryTilesEnableQueryEditing:disabled"/>
   <int value="1803465156" label="enable-zero-suggest-most-visited"/>
@@ -83687,6 +83708,15 @@
   <int value="1" label="ServiceWorker"/>
 </enum>
 
+<enum name="WrappingKeyDerivation">
+  <int value="0" label="Backed by scrypt"/>
+  <int value="1" label="Backed by LECredential"/>
+  <int value="2" label="Backed by Signature Challenge"/>
+  <int value="3" label="Backed by TPM/GSC bound to PCR"/>
+  <int value="4" label="Backed by TPM/GSC not bound to PCR"/>
+  <int value="5" label="(Deprecated) Backed by scrypt and TPM/GSC"/>
+</enum>
+
 <enum name="WrenchMenuAction">
   <int value="0" label="New tab"/>
   <int value="1" label="New window"/>
diff --git a/tools/metrics/histograms/histograms_xml/apps/histograms.xml b/tools/metrics/histograms/histograms_xml/apps/histograms.xml
index ddb06cf..27f220d 100644
--- a/tools/metrics/histograms/histograms_xml/apps/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/apps/histograms.xml
@@ -503,7 +503,7 @@
 </histogram>
 
 <histogram name="Apps.AppList.UserEvent.Error" enum="AppListSearchResult"
-    expires_after="2021-04-04">
+    expires_after="2021-10-04">
   <owner>tby@chromium.org</owner>
   <owner>thanhdng@chromium.org</owner>
   <summary>
@@ -1037,6 +1037,9 @@
 
 <histogram name="Apps.AppListSearchAbandonQueryLength" units="characters"
     expires_after="2021-03-15">
+  <obsolete>
+    Removed March 2021.
+  </obsolete>
   <owner>jennyz@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <owner>thanhdng@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/bluetooth/histograms.xml b/tools/metrics/histograms/histograms_xml/bluetooth/histograms.xml
index bf044c2..5d10c743 100644
--- a/tools/metrics/histograms/histograms_xml/bluetooth/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/bluetooth/histograms.xml
@@ -76,7 +76,7 @@
 </histogram>
 
 <histogram name="Bluetooth.ChromeOS.Pairing.Duration.Failure" units="ms"
-    expires_after="2021-03-05">
+    expires_after="2022-03-05">
 <!-- Name completed by histogram_suffixes name="BluetoothTransportTypes" -->
 
   <owner>hansberry@chromium.org</owner>
@@ -90,7 +90,7 @@
 </histogram>
 
 <histogram name="Bluetooth.ChromeOS.Pairing.Duration.Success" units="ms"
-    expires_after="2021-03-05">
+    expires_after="2022-03-05">
 <!-- Name completed by histogram_suffixes name="BluetoothTransportTypes" -->
 
   <owner>hansberry@chromium.org</owner>
@@ -137,7 +137,7 @@
 </histogram>
 
 <histogram name="Bluetooth.ChromeOS.Pairing.TransportType"
-    enum="BluetoothTransportType" expires_after="2021-03-05">
+    enum="BluetoothTransportType" expires_after="2022-03-05">
   <owner>hansberry@chromium.org</owner>
   <owner>cros-system-services-networking@google.com</owner>
   <summary>
@@ -167,7 +167,7 @@
 
 <histogram
     name="Bluetooth.ChromeOS.UserInitiatedReconnectionAttempt.Result.FailureReason"
-    enum="BluetoothConnectionFailureReason" expires_after="2021-03-05">
+    enum="BluetoothConnectionFailureReason" expires_after="2022-03-05">
 <!-- Name completed by histogram_suffixes name="BluetoothUISurfaces" -->
 
   <owner>hansberry@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/content/histograms.xml b/tools/metrics/histograms/histograms_xml/content/histograms.xml
index 85ea2f0..42d5408 100644
--- a/tools/metrics/histograms/histograms_xml/content/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/content/histograms.xml
@@ -580,7 +580,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.ActivityLoggingEnabled" enum="Boolean"
-    expires_after="2021-08-01">
+    expires_after="2022-03-01">
   <owner>rogerm@chromium.org</owner>
   <owner>feed@chromium.org</owner>
   <summary>
@@ -590,7 +590,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.AppLifecycle.Events"
-    enum="AppLifecycleEvent" expires_after="2021-06-20">
+    enum="AppLifecycleEvent" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -602,7 +605,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.AppLifecycle.NumRowsForDeletion"
-    units="count" expires_after="2020-10-01">
+    units="count" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -615,7 +621,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.AvailableOffline.Opened"
-    enum="Boolean" expires_after="2021-06-06">
+    enum="Boolean" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -630,7 +639,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.AvailableOffline.Shown" enum="Boolean"
-    expires_after="2021-04-04">
+    expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -657,7 +669,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.ContentAgeOnLoad.BlockingRefresh"
-    units="ms" expires_after="2022-01-20">
+    units="ms" expires_after="2022-03-01">
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -671,7 +683,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.ContentAgeOnLoad.NotRefreshed"
-    units="ms" expires_after="2022-01-20">
+    units="ms" expires_after="2022-03-01">
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -695,7 +707,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.Count" units="entries"
-    expires_after="M90">
+    expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -744,7 +759,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.FetchPendingSpinner.Shown"
-    enum="FeedSpinnerType" expires_after="2021-07-04">
+    enum="FeedSpinnerType" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -756,7 +774,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.FetchPendingSpinner.VisibleDuration"
-    units="ms" expires_after="2021-06-20">
+    units="ms" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -769,7 +790,10 @@
 
 <histogram
     name="ContentSuggestions.Feed.FetchPendingSpinner.VisibleDurationWithoutCompleting"
-    units="ms" expires_after="M90">
+    units="ms" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -781,7 +805,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.ImageFetchStatus"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2021-08-09">
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
   <summary>
@@ -792,7 +816,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.InterestHeader.NotInterestedInSource"
-    units="index" expires_after="2021-02-14">
+    units="index" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -803,7 +830,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.InterestHeader.NotInterestedInTopic"
-    units="index" expires_after="2021-02-14">
+    units="index" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -814,7 +844,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.InternalError"
-    enum="FeedInternalError" expires_after="M90">
+    enum="FeedInternalError" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -822,7 +855,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.LoadKeysTime" units="ms"
-    expires_after="M90">
+    expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -832,7 +868,7 @@
 </histogram>
 
 <histogram base="true" name="ContentSuggestions.Feed.LoadStepLatency"
-    units="ms" expires_after="2021-08-01">
+    units="ms" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
   <summary>
@@ -846,7 +882,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.LoadStreamStatus.BackgroundRefresh"
-    enum="FeedLoadStreamStatus" expires_after="2021-09-05">
+    enum="FeedLoadStreamStatus" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>carlosk@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -856,7 +892,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.LoadStreamStatus.Initial"
-    enum="FeedLoadStreamStatus" expires_after="2021-05-01">
+    enum="FeedLoadStreamStatus" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>carlosk@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -867,7 +903,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.LoadStreamStatus.InitialFromStore"
-    enum="FeedLoadStreamStatus" expires_after="2021-07-11">
+    enum="FeedLoadStreamStatus" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>carlosk@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -880,7 +916,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.LoadStreamStatus.LoadMore"
-    enum="FeedLoadStreamStatus" expires_after="2021-09-05">
+    enum="FeedLoadStreamStatus" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>carlosk@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -891,7 +927,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.LoadTime" units="ms"
-    expires_after="M90">
+    expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -901,7 +940,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.Network.Duration" units="ms"
-    expires_after="2021-09-05">
+    expires_after="2022-03-01">
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -912,7 +951,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.Network.Duration.{NetworkEvent}"
-    units="ms" expires_after="2022-01-13">
+    units="ms" expires_after="2022-03-01">
   <owner>sczs@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -939,7 +978,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.Network.RequestStatusCode"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2021-06-20">
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-03-01">
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -951,7 +990,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.Network.ResponseSizeKB" units="KB"
-    expires_after="2021-09-05">
+    expires_after="2022-03-01">
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -962,7 +1001,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.Network.ResponseStatus"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2021-09-05">
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-03-01">
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -972,7 +1011,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.Network.TokenDuration" units="ms"
-    expires_after="M90">
+    expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -982,7 +1024,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.Network.TokenFetchStatus"
-    enum="GoogleServiceAuthError" expires_after="2021-09-05">
+    enum="GoogleServiceAuthError" expires_after="2022-03-01">
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -993,7 +1035,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.NoticeCardFulfilled" enum="Boolean"
-    expires_after="2021-07-11">
+    expires_after="2022-03-01">
   <owner>vincb@google.com</owner>
   <owner>feed@chromium.org</owner>
   <summary>
@@ -1197,7 +1239,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.TimeSpentInFeed" units="ms"
-    expires_after="2021-09-05">
+    expires_after="2022-03-01">
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1267,7 +1309,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.UserActions" enum="FeedUserActionType"
-    expires_after="2021-09-05">
+    expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>carlosk@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1279,7 +1321,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.UserActions.Commands"
-    enum="FeedUserCommandType" expires_after="2021-09-01">
+    enum="FeedUserCommandType" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>sczs@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1291,7 +1333,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.UserJourney.GetMore.FailureDuration"
-    units="ms" expires_after="2021-07-11">
+    units="ms" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>carlosk@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1302,7 +1344,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.UserJourney.GetMore.SuccessDuration"
-    units="ms" expires_after="2021-07-11">
+    units="ms" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>carlosk@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1313,7 +1355,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.UserJourney.OpenCard.Failure"
-    enum="Boolean" expires_after="2021-09-05">
+    enum="Boolean" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>carlosk@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1324,7 +1366,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.UserJourney.OpenCard.SuccessDuration"
-    units="ms" expires_after="2021-09-05">
+    units="ms" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>carlosk@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1335,7 +1377,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.UserJourney.OpenFeed.FailureDuration"
-    units="ms" expires_after="2021-07-11">
+    units="ms" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>carlosk@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1347,7 +1389,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.UserJourney.OpenFeed.SuccessDuration"
-    units="ms" expires_after="2021-07-11">
+    units="ms" expires_after="2022-03-01">
   <owner>harringtond@chromium.org</owner>
   <owner>carlosk@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1377,7 +1419,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.VisualElement.Clicked" units="index"
-    expires_after="M90">
+    expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1392,7 +1437,10 @@
 
 <histogram
     name="ContentSuggestions.Feed.VisualElement.Clicked.TimeSinceElementFetched"
-    units="ms" expires_after="M90">
+    units="ms" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1405,7 +1453,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.VisualElement.Viewed" units="index"
-    expires_after="2021-07-04">
+    expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1421,7 +1472,10 @@
 
 <histogram
     name="ContentSuggestions.Feed.VisualElement.Viewed.TimeSinceElementFetched"
-    units="ms" expires_after="M90">
+    units="ms" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1435,7 +1489,10 @@
 
 <histogram
     name="ContentSuggestions.Feed.ZeroStateRefreshCompleted.ContentCount"
-    units="count" expires_after="M90">
+    units="count" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1447,7 +1504,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.ZeroStateRefreshCompleted.TokenCount"
-    units="count" expires_after="M90">
+    units="count" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1459,7 +1519,10 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.ZeroStateShown.Reason"
-    enum="FeedZeroStateShowReason" expires_after="2021-06-20">
+    enum="FeedZeroStateShowReason" expires_after="M89">
+  <obsolete>
+    Removed along with FeedV1 in M89
+  </obsolete>
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/cryptohome/histograms.xml b/tools/metrics/histograms/histograms_xml/cryptohome/histograms.xml
index 555acf5..2e52dbc 100644
--- a/tools/metrics/histograms/histograms_xml/cryptohome/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/cryptohome/histograms.xml
@@ -221,12 +221,16 @@
 
 <histogram name="Cryptohome.EvkkEncryptionType" enum="EvkkEncryptionType"
     expires_after="2021-08-01">
+  <obsolete>
+    Replaced with Cryptohome.WrappingKeyDerivation.Mount 2021-03-08 which
+    describes the derivation types more comprehensively.
+  </obsolete>
   <owner>enlightened@chromium.org</owner>
   <owner>tnagel@chromium.org</owner>
   <summary>
     Indicates whether Encrypted Vault Keyset Key (EVKK) is TPM wrapped. This is
     reported when the Derive function inside scrypt auth block or TPM auth block
-    is called, which happends during cryptohome mount phase.
+    is called, which happens during cryptohome mount phase.
   </summary>
 </histogram>
 
@@ -570,6 +574,20 @@
   </summary>
 </histogram>
 
+<histogram name="Cryptohome.WrappingKeyDerivation.Mount"
+    enum="WrappingKeyDerivation" expires_after="2022-03-01">
+  <owner>tnagel@chromium.org</owner>
+  <owner>zauri@chromium.org</owner>
+  <owner>cros-hwsec+uma@chromium.org</owner>
+  <owner>cros-privacy-core@google.com</owner>
+  <summary>
+    Derivation types of the Cryptohome's wrapping key. Reporting happens when
+    the Cryptohome is being mounted, which happens during a user creation, user
+    login, password change, migration from scrypt to TPM/GSC (in case the
+    security chip becomes available later).
+  </summary>
+</histogram>
+
 <histogram name="CryptohomeClient" units="ms" expires_after="2021-07-11">
   <owner>zuan@chromium.org</owner>
   <owner>cros-hwsec+uma@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/enterprise/histograms.xml b/tools/metrics/histograms/histograms_xml/enterprise/histograms.xml
index 4283ef8..00c27676 100644
--- a/tools/metrics/histograms/histograms_xml/enterprise/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/enterprise/histograms.xml
@@ -526,6 +526,138 @@
   </summary>
 </histogram>
 
+<histogram name="Enterprise.Dlp.CaptureModeInitBlocked" enum="BooleanBlocked"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Recorded when capture mode initialization was blocked by Data Leak
+    Prevention.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.ClipboardReadBlocked" enum="BooleanBlocked"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Result of Data Leak Prevention evaluation for a data read from the
+    clipboard.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.DataTransferControllerStarted" enum="Boolean"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Recorded when DataTransferController is initialized, meaning that all
+    clipboard and drag-n-drop operations will be affected by Data Leak
+    Prevention checks.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.DlpPolicyPresent" enum="Boolean"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Recorded when non-empty Data Leak Prevention policy is present and due to
+    that Data Leak Prevention infrastructure is initialized on the client.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.DragDropBlocked" enum="BooleanBlocked"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Result of Data Leak Prevention evaluation for a drag-n-drop operation.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.FilesDaemonStarted" enum="Boolean"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Recorded when Data Leak Prevention policy contains Files restrictions and
+    DLP Files daemon is started because of that.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.PrintingBlocked" enum="BooleanBlocked"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>Result of Data Leak Prevention evaluation for printing.</summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.PrivacyScreenEnforced" enum="BooleanForced"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Recorded when privacy screen was enforced by Data Leak Prevention.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.RestrictionConfigured"
+    enum="EnterpriseDlpPolicyRestriction" expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Records which types of restrictions are present in Data Leak Prevention
+    policy.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.ScreenShareBlocked" enum="BooleanBlocked"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Result of Data Leak Prevention evaluation for a screen share.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.ScreenSharePausedOrResumed" enum="Boolean"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Recorded when screen share was paused (True) or resumed (False) by Data Leak
+    Prevention.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.ScreenshotBlocked" enum="BooleanBlocked"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Result of Data Leak Prevention evaluation for taking a screenshot.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.VideoCaptureBlocked" enum="BooleanBlocked"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Result of Data Leak Prevention evaluation for a taking a video capture.
+  </summary>
+</histogram>
+
+<histogram name="Enterprise.Dlp.VideoCaptureInterrupted" enum="Boolean"
+    expires_after="2022-03-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>chromeos-dlp@google.com</owner>
+  <summary>
+    Recorded when video capture was interrupted by Data Leak Prevention.
+  </summary>
+</histogram>
+
 <histogram name="Enterprise.DMServerRequestSuccess"
     enum="EnterpriseDMServerRequestSuccess" expires_after="2021-08-09">
   <owner>poromov@chromium.org</owner>
@@ -907,6 +1039,9 @@
 
 <histogram name="Enterprise.LockToSingleUserResult"
     enum="LockToSingleUserResult" expires_after="M86">
+  <obsolete>
+    Removed in M91, enough data has been gathered.
+  </obsolete>
   <owner>emaxx@chromium.org</owner>
   <owner>igorcov@chromium.org</owner>
   <summary>
@@ -1217,6 +1352,9 @@
 
 <histogram base="true" name="Enterprise.RetrievePolicyResponse"
     enum="EnterpriseRetrievePolicyResponseType" expires_after="2020-02-16">
+  <obsolete>
+    Removed in M91 since the data is no longer monitored.
+  </obsolete>
 <!-- Name completed by histogram_suffixes name="EnterpriseRetrievePolicyResponse" -->
 
   <owner>emaxx@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
index 87e2f59..761aa33 100644
--- a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
@@ -5994,6 +5994,9 @@
 </histogram_suffixes>
 
 <histogram_suffixes name="EnterpriseRetrievePolicyResponse" separator=".">
+  <obsolete>
+    Removed in M91 since the data is no longer monitored.
+  </obsolete>
   <owner>emaxx@chromium.org</owner>
   <owner>igorcov@chromium.org</owner>
   <suffix name="Device" label="Device policy fetch response."/>
diff --git a/tools/metrics/histograms/histograms_xml/holding_space/histograms.xml b/tools/metrics/histograms/histograms_xml/holding_space/histograms.xml
index 92c61cf..e6a8f83 100644
--- a/tools/metrics/histograms/histograms_xml/holding_space/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/holding_space/histograms.xml
@@ -99,6 +99,16 @@
   </summary>
 </histogram>
 
+<histogram name="HoldingSpace.Item.FailureToLaunch" enum="HoldingSpaceItemType"
+    expires_after="2021-09-24">
+  <owner>dmblack@google.com</owner>
+  <owner>gzadina@google.com</owner>
+  <summary>
+    Records a failure to launch a holding space item of a specific type at the
+    moment of failure.
+  </summary>
+</histogram>
+
 <histogram name="HoldingSpace.Pod.Action.All" enum="HoldingSpacePodAction"
     expires_after="2021-09-24">
   <owner>dmblack@google.com</owner>
diff --git a/tools/metrics/histograms/histograms_xml/media/histograms.xml b/tools/metrics/histograms/histograms_xml/media/histograms.xml
index de19ca5..693d0a2d 100644
--- a/tools/metrics/histograms/histograms_xml/media/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/media/histograms.xml
@@ -2598,6 +2598,25 @@
   </summary>
 </histogram>
 
+<histogram name="Media.MojoVideoDecoderServiceTiming.{Impl}.{Method}"
+    units="ms" expires_after="M95">
+  <owner>dalecurtis@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
+  <summary>
+    The duration of MojoVideoDecoderService method calls. Recorded every call.
+  </summary>
+  <token key="Impl">
+    <variant name="Alternate"
+        summary="Timing for the kAlternate media::VideoDecoderImplementation."/>
+    <variant name="Default"
+        summary="Timing for the kDefault media::VideoDecoderImplementation."/>
+  </token>
+  <token key="Method">
+    <variant name="Construct" summary="The time taken for Construct()."/>
+    <variant name="Destruct" summary="Time time taken for destruction."/>
+  </token>
+</histogram>
+
 <histogram name="Media.MSE.AudioCodec" enum="MSECodec" expires_after="never">
 <!-- expires-never: Codec support planning metric. -->
 
diff --git a/tools/metrics/histograms/histograms_xml/mobile/histograms.xml b/tools/metrics/histograms/histograms_xml/mobile/histograms.xml
index 99cbf30b..33b709f 100644
--- a/tools/metrics/histograms/histograms_xml/mobile/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/mobile/histograms.xml
@@ -1023,7 +1023,7 @@
 </histogram>
 
 <histogram name="MobileSignInPromo.BookmarkManager.ImpressionsTilDismiss"
-    units="impressions" expires_after="2021-04-20">
+    units="impressions" expires_after="2022-04-01">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -1033,7 +1033,7 @@
 </histogram>
 
 <histogram name="MobileSignInPromo.BookmarkManager.ImpressionsTilSigninButtons"
-    units="impressions" expires_after="2021-04-20">
+    units="impressions" expires_after="2022-04-01">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -1045,7 +1045,7 @@
 </histogram>
 
 <histogram name="MobileSignInPromo.BookmarkManager.ImpressionsTilXButton"
-    units="impressions" expires_after="2021-08-09">
+    units="impressions" expires_after="2022-04-01">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -1055,7 +1055,7 @@
 </histogram>
 
 <histogram name="MobileSignInPromo.SettingsManager.ImpressionsTilDismiss"
-    units="impressions" expires_after="2021-04-20">
+    units="impressions" expires_after="2022-04-01">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -1065,7 +1065,7 @@
 </histogram>
 
 <histogram name="MobileSignInPromo.SettingsManager.ImpressionsTilSigninButtons"
-    units="impressions" expires_after="2021-08-22">
+    units="impressions" expires_after="2022-04-01">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -1077,7 +1077,7 @@
 </histogram>
 
 <histogram name="MobileSignInPromo.SettingsManager.ImpressionsTilXButton"
-    units="impressions" expires_after="2021-04-20">
+    units="impressions" expires_after="2022-04-01">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/notifications/histograms.xml b/tools/metrics/histograms/histograms_xml/notifications/histograms.xml
index 00c70a1..60cf475 100644
--- a/tools/metrics/histograms/histograms_xml/notifications/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/notifications/histograms.xml
@@ -30,6 +30,20 @@
   </summary>
 </histogram>
 
+<histogram name="Notifications.Android.Build" enum="BooleanSuccess"
+    expires_after="M96">
+  <owner>peter@chromium.org</owner>
+  <owner>knollr@chromium.org</owner>
+  <summary>
+    Records whether we could successfully build a notification using Android's
+    Notification Builder. If unsuccessful, we will silently drop the
+    notification display request. Note that delivering the notification may
+    still fail which is logged in Mobile.SystemNotification.NotifyFailure.
+    Logged when we try to build a notification using an instance of
+    NotificationWrapperBuilder.
+  </summary>
+</histogram>
+
 <histogram name="Notifications.Android.JobStartDelay" units="ms"
     expires_after="M96">
   <owner>peter@chromium.org</owner>
@@ -392,6 +406,35 @@
   </summary>
 </histogram>
 
+<histogram name="Notifications.macOS.ActionReceived.{Style}"
+    enum="BooleanValid" expires_after="M96">
+  <owner>knollr@chromium.org</owner>
+  <owner>peter@chromium.org</owner>
+  <summary>
+    Whether the received notification action data is valid or not. If it is, we
+    will continue to pass along the event. Logged on every action received by
+    the system (click or close) on any {Style} style notification.
+  </summary>
+  <token key="Style">
+    <variant name="Alert"/>
+    <variant name="Banner"/>
+  </token>
+</histogram>
+
+<histogram name="Notifications.macOS.Delivered.{Style}" enum="BooleanSuccess"
+    expires_after="M96">
+  <owner>knollr@chromium.org</owner>
+  <owner>peter@chromium.org</owner>
+  <summary>
+    Whether delivering a notification to the system was successful or not.
+    Logged every time we pass a notification with {Style} style to the system.
+  </summary>
+  <token key="Style">
+    <variant name="Alert"/>
+    <variant name="Banner"/>
+  </token>
+</histogram>
+
 <histogram name="Notifications.macOS.ServiceProcessKilled" units="ms"
     expires_after="M96">
   <owner>knollr@chromium.org</owner>
@@ -534,6 +577,16 @@
   </summary>
 </histogram>
 
+<histogram name="Notifications.PersistentWebNotificationClickEventResult"
+    enum="ServiceWorkerStatusCode" expires_after="2021-08-22">
+  <owner>peter@chromium.org</owner>
+  <owner>knollr@chromium.org</owner>
+  <summary>
+    Recorded delivery status for persistent notification clicks to a Service
+    Worker when handling a click on a persistent WebNotification has finished.
+  </summary>
+</histogram>
+
 <histogram name="Notifications.PersistentWebNotificationClickResult"
     enum="PlatformNotificationStatus" expires_after="2021-08-22">
   <owner>peter@chromium.org</owner>
@@ -544,6 +597,16 @@
   </summary>
 </histogram>
 
+<histogram name="Notifications.PersistentWebNotificationCloseEventResult"
+    enum="ServiceWorkerStatusCode" expires_after="2021-08-22">
+  <owner>peter@chromium.org</owner>
+  <owner>knollr@chromium.org</owner>
+  <summary>
+    Records delivery status for persistent notification close events sent to a
+    Service Worker when the event has been handled.
+  </summary>
+</histogram>
+
 <histogram name="Notifications.PersistentWebNotificationCloseResult"
     enum="PlatformNotificationStatus" expires_after="2021-08-22">
   <owner>peter@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/others/histograms.xml b/tools/metrics/histograms/histograms_xml/others/histograms.xml
index 2212fa39..6b79928 100644
--- a/tools/metrics/histograms/histograms_xml/others/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/others/histograms.xml
@@ -5882,14 +5882,14 @@
 </histogram>
 
 <histogram name="FirstRun.Sentinel.Created" enum="FirstRunSentinelResult"
-    expires_after="2021-08-22">
+    expires_after="2022-04-01">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>Result of sentinel file has been written.</summary>
 </histogram>
 
 <histogram name="FirstRun.Sentinel.CreatedFileError" enum="PlatformFileError"
-    expires_after="2021-04-20">
+    expires_after="2022-04-01">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>File error when the sentinel file was not written.</summary>
@@ -11879,8 +11879,10 @@
 <histogram name="ProximityAuth.BleWeaveConnectionResult"
     enum="ProximityAuth_BleWeaveConnectionResult" expires_after="2021-08-15">
   <owner>hansberry@chromium.org</owner>
+  <owner>better-together-dev@google.com</owner>
   <summary>
-    Provides a breakdown of how often each BLE weave connection result occurs.
+    Provides a breakdown of how often each BLE Weave connection result occurs.
+    Emitted to when a Weave connection attempt finishes.
   </summary>
 </histogram>
 
@@ -11888,9 +11890,11 @@
     enum="ProximityAuth_BluetoothGattConnectionResult"
     expires_after="2021-08-15">
   <owner>hansberry@chromium.org</owner>
+  <owner>better-together-dev@google.com</owner>
   <summary>
     Provides a breakdown of how many times each possible Bluetooth GATT
-    connection result occurs.
+    connection result occurs. Emitted to when a GATT connection attempt
+    finishes.
 
     The bucket &quot;Unknown result&quot; indicates that the Bluetooth platform
     returned an unknown error code; if it has any counts, the client code should
@@ -11902,9 +11906,11 @@
     enum="ProximityAuth_BluetoothGattServiceOperationResult"
     expires_after="2021-08-01">
   <owner>hansberry@chromium.org</owner>
+  <owner>better-together-dev@google.com</owner>
   <summary>
     Provides a breakdown of how many times each possible Bluetooth GATT
-    &quot;notify session&quot; attempt result occurs.
+    &quot;notify session&quot; attempt result occurs. Emitted to when a
+    &quot;notify session&quot; attempt attempt finishes.
 
     The bucket &quot;Unknown result&quot; indicates that the Bluetooth platform
     returned an unknown error code; if it has any counts, the client code should
@@ -11914,11 +11920,13 @@
 
 <histogram name="ProximityAuth.BluetoothGattWriteCharacteristicResult"
     enum="ProximityAuth_BluetoothGattServiceOperationResult"
-    expires_after="2021-03-21">
+    expires_after="2022-03-21">
   <owner>hansberry@chromium.org</owner>
+  <owner>better-together-dev@google.com</owner>
   <summary>
     Provides a breakdown of how many times each possible Bluetooth GATT
-    &quot;write characteristic&quot; attempt result occurs.
+    &quot;write characteristic&quot; attempt result occurs. Emitted to when a
+    &quot;write characteristic&quot; attempt attempt finishes.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/histograms_xml/quota/histograms.xml b/tools/metrics/histograms/histograms_xml/quota/histograms.xml
index 10d1959..c248e283 100644
--- a/tools/metrics/histograms/histograms_xml/quota/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/quota/histograms.xml
@@ -32,8 +32,9 @@
 </histogram>
 
 <histogram name="Quota.AgeOfOriginInDays" units="days"
-    expires_after="2021-04-18">
+    expires_after="2021-03-09">
   <owner>jarrydg@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     How many days it's been since an origin's temporary storage has been
     accessed. Logged hourly for all origins with stored data.
diff --git a/tools/metrics/histograms/histograms_xml/signin/histograms.xml b/tools/metrics/histograms/histograms_xml/signin/histograms.xml
index 4bdc7b2..d6b9aa2 100644
--- a/tools/metrics/histograms/histograms_xml/signin/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/signin/histograms.xml
@@ -84,7 +84,7 @@
 </histogram>
 
 <histogram name="Signin.AccountReconcilorState.OnGaiaResponse"
-    enum="SigninAccountReconcilorState" expires_after="2021-04-20">
+    enum="SigninAccountReconcilorState" expires_after="2022-04-01">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -126,6 +126,17 @@
   </summary>
 </histogram>
 
+<histogram name="Signin.AndroidAccountInfoFetchTime" units="ms"
+    expires_after="2021-08-16">
+  <owner>aliceywang@chromium.org</owner>
+  <owner>chrome-signin-team@google.com</owner>
+  <summary>
+    The time it takes to fetch the account information (user names, avatar) for
+    one account on Android. This is recorded when account information is
+    downloaded in FRE or when there is an accounts change event.
+  </summary>
+</histogram>
+
 <histogram name="Signin.AndroidAccountSigninViewSeedingTime" units="ms"
     expires_after="M89">
   <owner>bsazonov@chromium.org</owner>
@@ -1164,7 +1175,7 @@
 </histogram>
 
 <histogram name="Signin.SSOWKWebView.GetAllCookies.Duration" units="ms"
-    expires_after="2021-04-19">
+    expires_after="2022-04-01">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -1178,7 +1189,7 @@
 </histogram>
 
 <histogram name="Signin.SSOWKWebView.GetAllCookies.Request"
-    enum="SigninSSOWKWebViewGetAllCookiesRequest" expires_after="2021-04-19">
+    enum="SigninSSOWKWebViewGetAllCookiesRequest" expires_after="2022-04-01">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 41a1f66f..7e86d08 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -9473,6 +9473,14 @@
       Whether the page allows the user to zoom in/out.
     </summary>
   </metric>
+  <metric name="BadTapTargetsRatio">
+    <summary>
+      Percentage of tap targets whose center position is within another tap
+      target (expanded by a margin). The detail of the algorithm is
+      go/bad-tap-target-ukm. If evaluation time budget exceeded, this will be is
+      -2.
+    </summary>
+  </metric>
   <metric name="SmallTextRatio">
     <summary>
       Percentage of small font text area in total text area.
diff --git a/tools/polymer/polymer.py b/tools/polymer/polymer.py
index 61a7b24..2127dfc 100644
--- a/tools/polymer/polymer.py
+++ b/tools/polymer/polymer.py
@@ -159,13 +159,14 @@
         self.input_scheme = 'chrome'
       return POLYMER_V3_DIR + 'polymer/polymer_bundled.min.js'
 
+    extension = ('.js'
+                 if self.html_path_normalized in _migrated_imports else '.m.js')
+
     if re.match(r'ui/webui/resources/html/', self.html_path_normalized):
       return (self.html_path_normalized
           .replace(r'ui/webui/resources/html/', 'ui/webui/resources/js/')
-          .replace(r'.html', '.m.js'))
+          .replace(r'.html', extension))
 
-    extension = (
-        '.js' if self.html_path_normalized in _migrated_imports else '.m.js')
     return self.html_path_normalized.replace(r'.html', extension)
 
   def _to_js(self):
diff --git a/tools/polymer/polymer_test.py b/tools/polymer/polymer_test.py
index 7df09704..cefa149 100755
--- a/tools/polymer/polymer_test.py
+++ b/tools/polymer/polymer_test.py
@@ -117,6 +117,7 @@
     self._additional_flags = [
       '--migrated_imports',
       'tools/polymer/tests/foo.html',
+      'ui/webui/resources/html/ignore_me.html',
     ]
     self._run_test('dom-module', 'dom_module.html', 'dom_module.js',
                    'dom_module.m.js',
diff --git a/tools/polymer/tests/dom_module_with_migrated_imports_expected.js b/tools/polymer/tests/dom_module_with_migrated_imports_expected.js
index 80a4e4b..580e391 100644
--- a/tools/polymer/tests/dom_module_with_migrated_imports_expected.js
+++ b/tools/polymer/tests/dom_module_with_migrated_imports_expected.js
@@ -1,7 +1,7 @@
 import {Polymer, html} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {PaperRippleBehavior} from '//resources/polymer/v3_0/paper-behaviors/paper-ripple-behavior.js';
-import '//resources/js/ignore_me.m.js';
+import '//resources/js/ignore_me.js';
 import '../shared_vars_css.m.js';
 import './foo.js';
 
diff --git a/ui/accessibility/ax_enum_util.cc b/ui/accessibility/ax_enum_util.cc
index e2660e7..530f859 100644
--- a/ui/accessibility/ax_enum_util.cc
+++ b/ui/accessibility/ax_enum_util.cc
@@ -340,8 +340,6 @@
       return "iframePresentational";
     case ax::mojom::Role::kIgnored:
       return "ignored";
-    case ax::mojom::Role::kImageMap:
-      return "imageMap";
     case ax::mojom::Role::kImage:
       return "image";
     case ax::mojom::Role::kImeCandidate:
diff --git a/ui/accessibility/ax_enums.mojom b/ui/accessibility/ax_enums.mojom
index cd405e5..dd07184c 100644
--- a/ui/accessibility/ax_enums.mojom
+++ b/ui/accessibility/ax_enums.mojom
@@ -214,7 +214,6 @@
   kIframePresentational,
   kIgnored,
   kImage,
-  kImageMap,
   kImeCandidate,
   kInlineTextBox,
   kInputTime,
diff --git a/ui/accessibility/ax_node.cc b/ui/accessibility/ax_node.cc
index 227e01d..acb95cd2 100644
--- a/ui/accessibility/ax_node.cc
+++ b/ui/accessibility/ax_node.cc
@@ -1499,11 +1499,12 @@
     // contents. See https://crbug.com/689204.
     // So we decided to not enforce the leafiness of buttons and expose all
     // children.
+    // Images are not leaves because the same role is used for image maps,
+    // which can have link and/or text children.
     case ax::mojom::Role::kButton:
       return false;
     case ax::mojom::Role::kDocCover:
     case ax::mojom::Role::kGraphicsSymbol:
-    case ax::mojom::Role::kImage:
     case ax::mojom::Role::kMeter:
     case ax::mojom::Role::kScrollBar:
     case ax::mojom::Role::kSlider:
diff --git a/ui/accessibility/ax_role_properties.cc b/ui/accessibility/ax_role_properties.cc
index 95bf0582..4f28632 100644
--- a/ui/accessibility/ax_role_properties.cc
+++ b/ui/accessibility/ax_role_properties.cc
@@ -192,23 +192,23 @@
   switch (role) {
     case ax::mojom::Role::kSplitter:
       return isFocusable;
-    case ax::mojom::Role::kTreeItem:
     case ax::mojom::Role::kDate:
     case ax::mojom::Role::kDateTime:
-    case ax::mojom::Role::kInputTime:
     case ax::mojom::Role::kDocBackLink:
     case ax::mojom::Role::kDocBiblioRef:
     case ax::mojom::Role::kDocGlossRef:
     case ax::mojom::Role::kDocNoteRef:
+    case ax::mojom::Role::kInputTime:
     case ax::mojom::Role::kLink:
+    case ax::mojom::Role::kTreeItem:
       return true;
+    case ax::mojom::Role::kAlert:
+    case ax::mojom::Role::kDialog:
     case ax::mojom::Role::kMenu:
     case ax::mojom::Role::kMenuBar:
     case ax::mojom::Role::kNone:
-    case ax::mojom::Role::kUnknown:
     case ax::mojom::Role::kTree:
-    case ax::mojom::Role::kDialog:
-    case ax::mojom::Role::kAlert:
+    case ax::mojom::Role::kUnknown:
       return false;
     default:
       return IsControl(role);
@@ -280,7 +280,6 @@
     case ax::mojom::Role::kDocCover:
     case ax::mojom::Role::kGraphicsSymbol:
     case ax::mojom::Role::kImage:
-    case ax::mojom::Role::kImageMap:
     case ax::mojom::Role::kSvgRoot:
       return true;
     default:
@@ -754,7 +753,6 @@
     case ax::mojom::Role::kDocument:
     case ax::mojom::Role::kGraphicsDocument:
     case ax::mojom::Role::kImage:
-    case ax::mojom::Role::kImageMap:
     case ax::mojom::Role::kList:
     case ax::mojom::Role::kListItem:
     case ax::mojom::Role::kPdfRoot:
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.cc b/ui/accessibility/platform/ax_platform_node_auralinux.cc
index b1d16d65..151246de 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -2803,9 +2803,7 @@
     case ax::mojom::Role::kIgnored:
       return ATK_ROLE_REDUNDANT_OBJECT;
     case ax::mojom::Role::kImage:
-      return ATK_ROLE_IMAGE;
-    case ax::mojom::Role::kImageMap:
-      return ATK_ROLE_IMAGE_MAP;
+      return IsImageWithMap() ? ATK_ROLE_IMAGE_MAP : ATK_ROLE_IMAGE;
     case ax::mojom::Role::kInlineTextBox:
       return kStaticRole;
     case ax::mojom::Role::kInputTime:
diff --git a/ui/accessibility/platform/ax_platform_node_base.cc b/ui/accessibility/platform/ax_platform_node_base.cc
index 4744ecf9..027c3a4 100644
--- a/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_base.cc
@@ -702,6 +702,12 @@
   return GetDelegate()->GetLocalizedStringForRoleDescription();
 }
 
+bool AXPlatformNodeBase::IsImageWithMap() const {
+  DCHECK_EQ(GetData().role, ax::mojom::Role::kImage)
+      << "Only call IsImageWithMap() on an image";
+  return GetChildCount();
+}
+
 AXPlatformNodeBase* AXPlatformNodeBase::GetSelectionContainer() const {
   if (!delegate_)
     return nullptr;
diff --git a/ui/accessibility/platform/ax_platform_node_base.h b/ui/accessibility/platform/ax_platform_node_base.h
index 169c513..c7ac52a 100644
--- a/ui/accessibility/platform/ax_platform_node_base.h
+++ b/ui/accessibility/platform/ax_platform_node_base.h
@@ -390,6 +390,10 @@
   base::string16 GetRoleDescriptionFromImageAnnotationStatusOrFromAttribute()
       const;
 
+  // Return true if a kImage corresponds to an image map (has children).
+  // Cannot be called on nodes with a role other than kImage.
+  bool IsImageWithMap() const;
+
   // Cast a gfx::NativeViewAccessible to an AXPlatformNodeBase if it is one,
   // or return NULL if it's not an instance of this class.
   static AXPlatformNodeBase* FromNativeViewAccessible(
diff --git a/ui/accessibility/platform/ax_platform_node_mac.mm b/ui/accessibility/platform/ax_platform_node_mac.mm
index 3ef8956..e874bb0 100644
--- a/ui/accessibility/platform/ax_platform_node_mac.mm
+++ b/ui/accessibility/platform/ax_platform_node_mac.mm
@@ -142,7 +142,6 @@
       {ax::mojom::Role::kIframePresentational, NSAccessibilityGroupRole},
       {ax::mojom::Role::kIgnored, NSAccessibilityUnknownRole},
       {ax::mojom::Role::kImage, NSAccessibilityImageRole},
-      {ax::mojom::Role::kImageMap, NSAccessibilityGroupRole},
       {ax::mojom::Role::kInputTime, @"AXTimeField"},
       {ax::mojom::Role::kLabelText, NSAccessibilityGroupRole},
       {ax::mojom::Role::kLayoutTable, NSAccessibilityGroupRole},
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index 44d49b9..f23c478 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -5334,7 +5334,6 @@
       return ROLE_SYSTEM_GROUPING;
 
     case ax::mojom::Role::kImage:
-    case ax::mojom::Role::kImageMap:
       return ROLE_SYSTEM_GRAPHIC;
 
     case ax::mojom::Role::kInputTime:
@@ -5835,8 +5834,9 @@
     case ax::mojom::Role::kIframe:
       ia2_role = IA2_ROLE_INTERNAL_FRAME;
       break;
-    case ax::mojom::Role::kImageMap:
-      ia2_role = IA2_ROLE_IMAGE_MAP;
+    case ax::mojom::Role::kImage:
+      if (IsImageWithMap())
+        ia2_role = IA2_ROLE_IMAGE_MAP;
       break;
     case ax::mojom::Role::kLabelText:
     case ax::mojom::Role::kLegend:
@@ -6166,9 +6166,6 @@
     case ax::mojom::Role::kImage:
       return L"img";
 
-    case ax::mojom::Role::kImageMap:
-      return L"document";
-
     case ax::mojom::Role::kImeCandidate:
       // Internal role, not used on Windows.
       return L"group";
@@ -6841,10 +6838,8 @@
       return UIA_GroupControlTypeId;
 
     case ax::mojom::Role::kImage:
-      return UIA_ImageControlTypeId;
-
-    case ax::mojom::Role::kImageMap:
-      return UIA_DocumentControlTypeId;
+      return IsImageWithMap() ? UIA_DocumentControlTypeId
+                              : UIA_ImageControlTypeId;
 
     case ax::mojom::Role::kInputTime:
       return UIA_GroupControlTypeId;
diff --git a/ui/base/ime/fuchsia/OWNERS b/ui/base/ime/fuchsia/OWNERS
new file mode 100644
index 0000000..e7034ea
--- /dev/null
+++ b/ui/base/ime/fuchsia/OWNERS
@@ -0,0 +1 @@
+file://build/fuchsia/OWNERS
diff --git a/ui/chromeos/strings/BUILD.gn b/ui/chromeos/strings/BUILD.gn
index f41128a..27ed84e7 100644
--- a/ui/chromeos/strings/BUILD.gn
+++ b/ui/chromeos/strings/BUILD.gn
@@ -9,6 +9,6 @@
   source = "../ui_chromeos_strings.grd"
   outputs =
       [ "grit/ui_chromeos_strings.h" ] +
-      process_file_template(locales_with_fake_bidi,
+      process_file_template(locales_with_pseudolocales,
                             [ "ui_chromeos_strings_{{source_name_part}}.pak" ])
 }
diff --git a/ui/compositor/compositor.cc b/ui/compositor/compositor.cc
index 54d981d..99b4568 100644
--- a/ui/compositor/compositor.cc
+++ b/ui/compositor/compositor.cc
@@ -750,7 +750,11 @@
 }
 
 void Compositor::StopThroughtputTracker(TrackerId tracker_id) {
-  DCHECK(base::Contains(throughput_tracker_map_, tracker_id));
+  // TODO(crbug.com/1183374): DCHECKs are disabled during automated testing on
+  // CrOS and this check failed when tested on an experimental builder. Revert
+  // https://crrev.com/c/2727841 (or uncomment) to enable it. See
+  // go/chrome-dcheck-on-cros or http://crbug.com/1113456 for more details.
+  // DCHECK(base::Contains(throughput_tracker_map_, tracker_id));
   animation_host_->StopThroughputTracking(tracker_id);
 }
 
diff --git a/ui/events/fuchsia/OWNERS b/ui/events/fuchsia/OWNERS
index e69de29..e7034ea 100644
--- a/ui/events/fuchsia/OWNERS
+++ b/ui/events/fuchsia/OWNERS
@@ -0,0 +1 @@
+file://build/fuchsia/OWNERS
diff --git a/ui/events/ozone/evdev/libgestures_glue/gesture_feedback.cc b/ui/events/ozone/evdev/libgestures_glue/gesture_feedback.cc
index 4413616..71f2004 100644
--- a/ui/events/ozone/evdev/libgestures_glue/gesture_feedback.cc
+++ b/ui/events/ozone/evdev/libgestures_glue/gesture_feedback.cc
@@ -7,6 +7,8 @@
 #include <stddef.h>
 #include <time.h>
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/location.h"
@@ -243,8 +245,7 @@
       FROM_HERE,
       {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
        base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
-      base::BindOnce(&CompressDumpedLog,
-                     base::Passed(&log_paths_to_be_compressed)),
+      base::BindOnce(&CompressDumpedLog, std::move(log_paths_to_be_compressed)),
       base::BindOnce(std::move(reply), log_paths));
 }
 
diff --git a/ui/gtk/native_theme_gtk.cc b/ui/gtk/native_theme_gtk.cc
index 253bb05..90866ac6 100644
--- a/ui/gtk/native_theme_gtk.cc
+++ b/ui/gtk/native_theme_gtk.cc
@@ -73,13 +73,10 @@
     const ui::NativeTheme* base_theme,
     ui::NativeTheme::ColorScheme color_scheme) {
   switch (color_id) {
-    // Windows
     case ui::NativeTheme::kColorId_WindowBackground:
-    // Dialogs
     case ui::NativeTheme::kColorId_DialogBackground:
     case ui::NativeTheme::kColorId_BubbleBackground:
-    // Notifications
-    case ui::NativeTheme::kColorId_NotificationDefaultBackground:
+    case ui::NativeTheme::kColorId_NotificationBackground:
       return GetBgColor("");
     case ui::NativeTheme::kColorId_DialogForeground:
     case ui::NativeTheme::kColorId_AvatarIconIncognito:
diff --git a/ui/message_center/public/cpp/message_center_constants.h b/ui/message_center/public/cpp/message_center_constants.h
index c623637..8a265b5 100644
--- a/ui/message_center/public/cpp/message_center_constants.h
+++ b/ui/message_center/public/cpp/message_center_constants.h
@@ -89,8 +89,6 @@
 const int kButtonIconTopPadding = 11;      // In DIPs.
 const int kButtonIconToTitlePadding = 16;  // In DIPs.
 
-constexpr SkColor kHoveredButtonBackgroundColor = SkColorSetRGB(243, 243, 243);
-
 // Progress bar.
 const int kProgressBarTopPadding = 16;
 #if defined(OS_APPLE)
diff --git a/ui/message_center/views/message_view.cc b/ui/message_center/views/message_view.cc
index 5e3b914..e8a0491 100644
--- a/ui/message_center/views/message_view.cc
+++ b/ui/message_center/views/message_view.cc
@@ -473,8 +473,11 @@
 }
 
 void MessageView::UpdateBackgroundPainter() {
-  SkColor background_color = GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_NotificationDefaultBackground);
+  auto* theme = GetNativeTheme();
+  SkColor background_color = theme->GetSystemColor(
+      is_active_ ? ui::NativeTheme::kColorId_NotificationBackgroundActive
+                 : ui::NativeTheme::kColorId_NotificationBackground);
+
   SetBackground(views::CreateBackgroundFromPainter(
       std::make_unique<NotificationBackgroundPainter>(
           top_radius_, bottom_radius_, background_color)));
@@ -487,9 +490,8 @@
 }
 
 void MessageView::SetDrawBackgroundAsActive(bool active) {
-  background()->SetNativeControlColor(active ? kHoveredButtonBackgroundColor
-                                             : kNotificationBackgroundColor);
-  SchedulePaint();
+  is_active_ = active;
+  UpdateBackgroundPainter();
 }
 
 }  // namespace message_center
diff --git a/ui/message_center/views/message_view.h b/ui/message_center/views/message_view.h
index f8c2e3b..859ab8d 100644
--- a/ui/message_center/views/message_view.h
+++ b/ui/message_center/views/message_view.h
@@ -213,6 +213,10 @@
 
   base::string16 accessible_name_;
 
+  // Tracks whether background should be drawn as active based on gesture
+  // events.
+  bool is_active_ = false;
+
   // Flag if the notification is set to pinned or not. See the comment in
   // MessageView::Mode for detail.
   bool pinned_ = false;
diff --git a/ui/message_center/views/notification_view_md.cc b/ui/message_center/views/notification_view_md.cc
index 1645ad0..2b607ac 100644
--- a/ui/message_center/views/notification_view_md.cc
+++ b/ui/message_center/views/notification_view_md.cc
@@ -476,7 +476,7 @@
     SetEnabledTextColors(GetTextColor());
     label()->SetAutoColorReadabilityEnabled(true);
     label()->SetBackgroundColor(GetNativeTheme()->GetSystemColor(
-        ui::NativeTheme::kColorId_NotificationInlineSettingsBackground));
+        ui::NativeTheme::kColorId_NotificationBackgroundActive));
   }
 
  private:
@@ -1379,8 +1379,8 @@
   bool inline_settings_visible = settings_row_ && settings_row_->GetVisible();
   return GetNativeTheme()->GetSystemColor(
       inline_settings_visible
-          ? ui::NativeTheme::kColorId_NotificationInlineSettingsBackground
-          : ui::NativeTheme::kColorId_NotificationDefaultBackground);
+          ? ui::NativeTheme::kColorId_NotificationBackgroundActive
+          : ui::NativeTheme::kColorId_NotificationBackground);
 }
 
 void NotificationViewMD::UpdateActionButtonsRowBackground() {
@@ -1477,7 +1477,7 @@
 
 SkColor NotificationViewMD::GetInkDropBaseColor() const {
   return GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_NotificationInlineSettingsBackground);
+      ui::NativeTheme::kColorId_NotificationBackgroundActive);
 }
 
 void NotificationViewMD::InkDropAnimationStarted() {
diff --git a/ui/native_theme/common_theme.cc b/ui/native_theme/common_theme.cc
index dd7307f..9b1b01b 100644
--- a/ui/native_theme/common_theme.cc
+++ b/ui/native_theme/common_theme.cc
@@ -321,7 +321,7 @@
     }
 
     // Notification
-    case NativeTheme::kColorId_NotificationDefaultBackground:
+    case NativeTheme::kColorId_NotificationBackground:
     case NativeTheme::kColorId_NotificationColor:
       return base_theme->GetUnprocessedSystemColor(
           NativeTheme::kColorId_WindowBackground, color_scheme);
@@ -332,7 +332,7 @@
     case NativeTheme::kColorId_NotificationPlaceholderColor:
       return SkColorSetA(SK_ColorWHITE, gfx::kDisabledControlAlpha);
     case NativeTheme::kColorId_NotificationActionsRowBackground:
-    case NativeTheme::kColorId_NotificationInlineSettingsBackground: {
+    case NativeTheme::kColorId_NotificationBackgroundActive: {
       const SkColor bg = base_theme->GetUnprocessedSystemColor(
           NativeTheme::kColorId_WindowBackground, color_scheme);
       return color_utils::BlendTowardMaxContrast(bg, 0x14);
diff --git a/ui/native_theme/native_theme_color_id.h b/ui/native_theme/native_theme_color_id.h
index 8f89aa1..da45543 100644
--- a/ui/native_theme/native_theme_color_id.h
+++ b/ui/native_theme/native_theme_color_id.h
@@ -88,9 +88,9 @@
   OP(kColorId_OverlayScrollbarThumbHoveredStroke),                             \
   OP(kColorId_OverlayScrollbarThumbStroke),                                    \
   /* Notification view */                                                      \
-  OP(kColorId_NotificationDefaultBackground),                                  \
+  OP(kColorId_NotificationBackground),                                         \
+  OP(kColorId_NotificationBackgroundActive),                                   \
   OP(kColorId_NotificationActionsRowBackground),                               \
-  OP(kColorId_NotificationInlineSettingsBackground),                           \
   OP(kColorId_NotificationLargeImageBackground),                               \
   OP(kColorId_NotificationColor),                                              \
   OP(kColorId_NotificationPlaceholderColor),                                   \
diff --git a/ui/native_theme/native_theme_win.cc b/ui/native_theme/native_theme_win.cc
index 688e0ff..a3ef415 100644
--- a/ui/native_theme/native_theme_win.cc
+++ b/ui/native_theme/native_theme_win.cc
@@ -617,7 +617,7 @@
     case kColorId_TableBackgroundAlternate:
     case kColorId_TooltipBackground:
     case kColorId_ProminentButtonDisabledColor:
-    case kColorId_NotificationDefaultBackground:
+    case kColorId_NotificationBackground:
       return system_colors_[SystemThemeColor::kWindow];
 
     // Window Text
diff --git a/ui/strings/BUILD.gn b/ui/strings/BUILD.gn
index 04a4650..09ee0bb5 100644
--- a/ui/strings/BUILD.gn
+++ b/ui/strings/BUILD.gn
@@ -17,7 +17,7 @@
 grit("ui_strings") {
   source = "ui_strings.grd"
   outputs = [ "grit/ui_strings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "ui_strings_$locale.pak" ]
   }
 }
@@ -25,7 +25,7 @@
 grit("app_locale_settings") {
   source = "app_locale_settings.grd"
   outputs = [ "grit/app_locale_settings.h" ]
-  foreach(locale, locales_with_fake_bidi) {
+  foreach(locale, locales_with_pseudolocales) {
     outputs += [ "app_locale_settings_$locale.pak" ]
   }
 }
diff --git a/ui/views/examples/README.md b/ui/views/examples/README.md
index deaa453..cab2627 100644
--- a/ui/views/examples/README.md
+++ b/ui/views/examples/README.md
@@ -32,6 +32,7 @@
 - Message Box View
 - Multiline RenderText
 - Native Theme Colors
+- Notification
 - Progress Bar
 - Radio Button
 - Scroll View
diff --git a/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.html b/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.html
index 096fa65..3bbde5b 100644
--- a/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.html
+++ b/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.html
@@ -92,7 +92,7 @@
         </cr-input>
         <div id="authentication-method"
             hidden="[[!shouldShowAuthenticationUI_(isActiveDirectory_,
-                isKerberosEnabled_)]]">
+                isKerberosEnabled_, isGuest_)]]">
           <label id="authentication-label" class="cr-form-field-label">
             [[i18n('smbShareAuthenticationMethod')]]
           </label>
diff --git a/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.js b/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.js
index 866d8e0..4396fe7 100644
--- a/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.js
+++ b/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.js
@@ -98,13 +98,30 @@
     },
 
     /** @private */
+    isGuest_: {
+      type: Boolean,
+      value() {
+        return loadTimeData.getBoolean('isGuest');
+      },
+    },
+
+    /** @private */
     authenticationMethod_: {
       type: String,
       value() {
-        return loadTimeData.getBoolean('isActiveDirectoryUser') ||
-                loadTimeData.getBoolean('isKerberosEnabled') ?
-            SmbAuthMethod.KERBEROS :
-            SmbAuthMethod.CREDENTIALS;
+        // SSO not supported in guest mode. TODO(crbug/1186188): Enable SSO
+        // option for MGS on file share UI, after fixing authentication error.
+        if (loadTimeData.getBoolean('isGuest')) {
+          return SmbAuthMethod.CREDENTIALS;
+        }
+
+        // SSO only supported on ChromAD or Kerberos.
+        if (loadTimeData.getBoolean('isActiveDirectoryUser') ||
+            loadTimeData.getBoolean('isKerberosEnabled')) {
+          return SmbAuthMethod.KERBEROS;
+        }
+
+        return SmbAuthMethod.CREDENTIALS;
       },
       observer: 'onAuthenticationMethodChanged_',
     },
@@ -214,6 +231,13 @@
    * @private
    */
   shouldShowAuthenticationUI_() {
+    // SSO not supported in guest mode. TODO(crbug/1186188): Enable SSO option
+    // for MGS on file share UI, after fixing authentication error.
+    if (this.isGuest_) {
+      return false;
+    }
+
+    // SSO only supported on ChromAD or Kerberos.
     return this.isActiveDirectory_ || this.isKerberosEnabled_;
   },
 
@@ -246,7 +270,6 @@
             loadTimeData.getString('smbShareAddedInvalidUsernameMessage'));
         break;
 
-
       // Path Errors
       case SmbMountResult.NOT_FOUND:
         this.setPathError_(
diff --git a/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.html b/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.html
index 859cbb3..026c48b 100644
--- a/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.html
+++ b/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.html
@@ -7,6 +7,7 @@
   <template>
     <style>
       :host {
+        --cr-icon-button-fill-color: var(--google-grey-refresh-700);
         --cr-icon-button-icon-start-offset: 0;
         --cr-icon-button-icon-size: 20px;
         --cr-icon-button-size: 36px;
diff --git a/ui/webui/resources/js/BUILD.gn b/ui/webui/resources/js/BUILD.gn
index b4dd498..f004caa5 100644
--- a/ui/webui/resources/js/BUILD.gn
+++ b/ui/webui/resources/js/BUILD.gn
@@ -45,6 +45,7 @@
     "cr.js",
     "cr.m.js",
     "cr/ui.js",
+    "custom_element.js",
     "event_tracker.js",
     "i18n_template_no_process.js",
     "icon.js",
@@ -257,6 +258,7 @@
   deps = [
     ":assert.m",
     ":cr.m",
+    ":custom_element",
     ":event_tracker.m",
     ":i18n_behavior.m",
     ":icon.m",
@@ -284,6 +286,9 @@
   externs_list = [ "$externs_path/chrome_send.js" ]
 }
 
+js_library("custom_element") {
+}
+
 js_library("event_tracker.m") {
   sources = [ "$root_gen_dir/ui/webui/resources/js/event_tracker.m.js" ]
   extra_deps = [ ":modulize_local" ]
diff --git a/chrome/browser/resources/tab_strip/custom_element.js b/ui/webui/resources/js/custom_element.js
similarity index 82%
rename from chrome/browser/resources/tab_strip/custom_element.js
rename to ui/webui/resources/js/custom_element.js
index 50e2bda..9d74568 100644
--- a/chrome/browser/resources/tab_strip/custom_element.js
+++ b/ui/webui/resources/js/custom_element.js
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 /**
- * Super class for all custom elements defined in the tab strip.
+ * @fileoverview Base class for Web Components that don't use Polymer.
+ * See the following file for usage:
+ * chrome/test/data/webui/js/custom_element_test.js
  */
 export class CustomElement extends HTMLElement {
   constructor() {
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn
index b9d7e59..477299b 100644
--- a/weblayer/BUILD.gn
+++ b/weblayer/BUILD.gn
@@ -55,7 +55,7 @@
     outputs =
         weblayer_components_strings_java_resources +
         [ "grit/components_strings.h" ] +
-        process_file_template(locales_with_fake_bidi,
+        process_file_template(locales_with_pseudolocales,
                               [ "components_strings_{{source_name_part}}.pak" ])
   }
 
@@ -73,7 +73,7 @@
     ]
     outputs = [ "grit/components_chromium_strings.h" ] +
               process_file_template(
-                  locales_with_fake_bidi,
+                  locales_with_pseudolocales,
                   [ "components_chromium_strings_{{source_name_part}}.pak" ])
   }
 
diff --git a/weblayer/browser/page_load_metrics_browsertest.cc b/weblayer/browser/page_load_metrics_browsertest.cc
index 08f8c2f..f4f50f0 100644
--- a/weblayer/browser/page_load_metrics_browsertest.cc
+++ b/weblayer/browser/page_load_metrics_browsertest.cc
@@ -25,22 +25,30 @@
   ~PageLoadMetricsObserver() override = default;
 
   // page_load_metrics::PageLoadMetricsObserver implementation:
+  void OnFirstPaintInPage(
+      const page_load_metrics::mojom::PageLoadTiming& timing) override {
+    on_first_paint_seen_ = true;
+    QuitRunLoopIfReady();
+  }
+
   void OnFirstContentfulPaintInPage(
       const page_load_metrics::mojom::PageLoadTiming& timing) override {
-    quit_closure_.Run();
+    on_first_contentful_paint_seen_ = true;
+    QuitRunLoopIfReady();
   }
 
  private:
+  void QuitRunLoopIfReady() {
+    if (on_first_paint_seen_ && on_first_contentful_paint_seen_)
+      quit_closure_.Run();
+  }
+
+  bool on_first_paint_seen_ = false;
+  bool on_first_contentful_paint_seen_ = false;
   base::RepeatingClosure quit_closure_;
 };
 
-// Constant failure over android tablet tester. See https://crbug.com/1179052.
-#if defined(OS_ANDROID)
-#define MAYBE_Heartbeat DISABLED_Heartbeat
-#else
-#define MAYBE_Heartbeat Heartbeat
-#endif
-IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, MAYBE_Heartbeat) {
+IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, Heartbeat) {
   base::HistogramTester histogram_tester;
   ASSERT_TRUE(embedded_test_server()->Start());