diff --git a/.gn b/.gn
index c7296bb..28522e1 100644
--- a/.gn
+++ b/.gn
@@ -159,8 +159,7 @@
   "//chrome/browser/permissions/*",
   "//chrome/browser/picture_in_picture/*",
   "//chrome/browser/plugins/*",
-
-  # "//chrome/browser/policy/*",  # 1 error on Windows
+  "//chrome/browser/policy/*",
   "//chrome/browser/predictors/*",
   "//chrome/browser/prefetch/*",
   "//chrome/browser/prefs/*",
diff --git a/DEPS b/DEPS
index 80af83bf..274e92541 100644
--- a/DEPS
+++ b/DEPS
@@ -175,11 +175,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': '68219bfacaa82884cc34815b2cc43966fa0f3c31',
+  'skia_revision': '5d1c163807059a46c5f5a3594837496340604c9d',
   # 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': '5dcf1b6646d1bf03d21883dd1413bd56493dc62e',
+  'v8_revision': 'd23973d20363290e76d9dbe51eb274bf08e51f93',
   # 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.
@@ -187,11 +187,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': '2319607679d7781ff9bab5e821a34574ecb0bcc3',
+  'angle_revision': '728b9178660eb8f192c53986f1c8a519baf0f638',
   # 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': '1cba0a9c3a8a1961514ac63cd3091c1a376fe84a',
+  'swiftshader_revision': '6480643d669af9f75589316c43ba079db1158a60',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -238,7 +238,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': '5d56a75005e3f5ce076e38b133b5ec8dde35c1dc',
+  'catapult_revision': 'c72a211d009068a83158684ab8dc6ff38e00f4a3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -246,7 +246,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': 'f9f8e4b0d42d1f45a4d6bf95fd28ccbf4cc3e271',
+  'devtools_frontend_revision': '08dd1a3ca3a55d7041b60f86c38077cae8cf1ede',
   # 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.
@@ -302,7 +302,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '61e170b3c5f67da84bb28aca17abe78208732c36',
+  'dawn_revision': 'd9c2e89ef1ea62e50dc4ebf4dc6b2ff6b53d4cdf',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -525,7 +525,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '4454d0be942d588fcea5a390fb78711c07513a6c',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '8c27dcf2e53f038ba97e4e6452143a8f223c35b7',
       'condition': 'checkout_ios',
   },
 
@@ -856,7 +856,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'eb17dbb23599aadf015f4b5cee7d592a0910e6eb',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'a5c914b63a0c343d08222e1fe2ad056c5218fece',
       'condition': 'checkout_linux',
   },
 
@@ -937,7 +937,7 @@
     Var('chromium_git') + '/codecs/libgav1.git' + '@' + '638ef84819f8b3cd614dcf63378fe4814aa4cb2a',
 
   'src/third_party/glslang/src':
-    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + '9c3204a1fde09ba7b98b1779047bf8d3491244a5',
+    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + '75de196cecab0d37faa0e3de30c5bf85ab4e43f6',
 
   'src/third_party/google_toolbox_for_mac/src': {
       'url': Var('chromium_git') + '/external/github.com/google/google-toolbox-for-mac.git' + '@' + Var('google_toolbox_for_mac_revision'),
@@ -1235,7 +1235,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'c98edc42336fbf508cec873654b7e13ee2866bc9',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '5a992d9caec71b8d17c1c500efb6c64a9bd3fc14',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1465,7 +1465,7 @@
   },
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '8d8bae65e67deed612bc6ec397d6f74481b6a59c',
+    Var('webrtc_git') + '/src.git' + '@' + '9f6ff832d99ae697de34640b0d348983d908604c',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1540,7 +1540,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8b2eec58cc7e7f938121f5d4e30051f01ac27117',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@a8ab783931f91f433cc7902a45962f69b436360a',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/BUILD.gn b/android_webview/browser/BUILD.gn
index 207926d3..b9730288 100644
--- a/android_webview/browser/BUILD.gn
+++ b/android_webview/browser/BUILD.gn
@@ -34,6 +34,7 @@
     "aw_browser_terminator.h",
     "aw_content_browser_client.cc",
     "aw_content_browser_client.h",
+    "aw_content_browser_client_receiver_bindings.cc",
     "aw_content_browser_overlay_manifest.cc",
     "aw_content_browser_overlay_manifest.h",
     "aw_contents.cc",
diff --git a/android_webview/browser/OWNERS b/android_webview/browser/OWNERS
index 2cb608d3..1bb27b6f 100644
--- a/android_webview/browser/OWNERS
+++ b/android_webview/browser/OWNERS
@@ -2,3 +2,5 @@
 per-file aw_content_browser_overlay_manifest.h=file://ipc/SECURITY_OWNERS
 per-file aw_content_browser_overlay_manifest.cc=set noparent
 per-file aw_content_browser_overlay_manifest.cc=file://ipc/SECURITY_OWNERS
+per-file aw_content_browser_receiver_bindings.cc=set noparent
+per-file aw_content_browser_receiver_bindings.cc=file://ipc/SECURITY_OWNERS
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index 4835af69..8f58248 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -54,7 +54,6 @@
 #include "build/build_config.h"
 #include "components/autofill/content/browser/content_autofill_driver_factory.h"
 #include "components/cdm/browser/cdm_message_filter_android.h"
-#include "components/cdm/browser/media_drm_storage_impl.h"
 #include "components/content_capture/browser/content_capture_receiver_manager.h"
 #include "components/crash/content/browser/crash_handler_host_linux.h"
 #include "components/navigation_interception/intercept_navigation_delegate.h"
@@ -89,7 +88,6 @@
 #include "content/public/common/url_constants.h"
 #include "content/public/common/user_agent.h"
 #include "content/public/common/web_preferences.h"
-#include "media/mojo/buildflags.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
@@ -227,46 +225,6 @@
   cookie_manager->SetMojoCookieManager(std::move(cookie_manager_remote));
 }
 
-#if BUILDFLAG(ENABLE_MOJO_CDM)
-void CreateOriginId(cdm::MediaDrmStorageImpl::OriginIdObtainedCB callback) {
-  std::move(callback).Run(true, base::UnguessableToken::Create());
-}
-
-void AllowEmptyOriginIdCB(base::OnceCallback<void(bool)> callback) {
-  // Since CreateOriginId() always returns a non-empty origin ID, we don't need
-  // to allow empty origin ID.
-  std::move(callback).Run(false);
-}
-
-void CreateMediaDrmStorage(
-    content::RenderFrameHost* render_frame_host,
-    mojo::PendingReceiver<::media::mojom::MediaDrmStorage> receiver) {
-  DCHECK(render_frame_host);
-
-  if (render_frame_host->GetLastCommittedOrigin().opaque()) {
-    DVLOG(1) << __func__ << ": Unique origin.";
-    return;
-  }
-
-  content::WebContents* web_contents =
-      content::WebContents::FromRenderFrameHost(render_frame_host);
-  DCHECK(web_contents) << "WebContents not available.";
-
-  auto* aw_browser_context =
-      static_cast<AwBrowserContext*>(web_contents->GetBrowserContext());
-  DCHECK(aw_browser_context) << "AwBrowserContext not available.";
-
-  PrefService* pref_service = aw_browser_context->GetPrefService();
-  DCHECK(pref_service);
-
-  // The object will be deleted on connection error, or when the frame navigates
-  // away.
-  new cdm::MediaDrmStorageImpl(
-      render_frame_host, pref_service, base::BindRepeating(&CreateOriginId),
-      base::BindRepeating(&AllowEmptyOriginIdCB), std::move(receiver));
-}
-#endif  // BUILDFLAG(ENABLE_MOJO_CDM)
-
 // Helper method that checks the RenderProcessHost is still alive before hopping
 // over to the IO thread.
 void MaybeCreateSafeBrowsing(
@@ -807,15 +765,6 @@
   return safe_browsing_url_checker_delegate_;
 }
 
-void AwContentBrowserClient::ExposeInterfacesToMediaService(
-    service_manager::BinderRegistry* registry,
-    content::RenderFrameHost* render_frame_host) {
-#if BUILDFLAG(ENABLE_MOJO_CDM)
-  registry->AddInterface(
-      base::BindRepeating(&CreateMediaDrmStorage, render_frame_host));
-#endif
-}
-
 bool AwContentBrowserClient::ShouldOverrideUrlLoading(
     int frame_tree_node_id,
     bool browser_initiated,
diff --git a/android_webview/browser/aw_content_browser_client.h b/android_webview/browser/aw_content_browser_client.h
index cefc8f4..d87f62f 100644
--- a/android_webview/browser/aw_content_browser_client.h
+++ b/android_webview/browser/aw_content_browser_client.h
@@ -142,9 +142,8 @@
       service_manager::BinderRegistry* registry,
       blink::AssociatedInterfaceRegistry* associated_registry,
       content::RenderProcessHost* render_process_host) override;
-  void ExposeInterfacesToMediaService(
-      service_manager::BinderRegistry* registry,
-      content::RenderFrameHost* render_frame_host) override;
+  void BindMediaServiceReceiver(content::RenderFrameHost* render_frame_host,
+                                mojo::GenericPendingReceiver receiver) override;
   std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
   CreateURLLoaderThrottles(
       const network::ResourceRequest& request,
diff --git a/android_webview/browser/aw_content_browser_client_receiver_bindings.cc b/android_webview/browser/aw_content_browser_client_receiver_bindings.cc
new file mode 100644
index 0000000..ada3758
--- /dev/null
+++ b/android_webview/browser/aw_content_browser_client_receiver_bindings.cc
@@ -0,0 +1,69 @@
+// 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 "android_webview/browser/aw_content_browser_client.h"
+
+#include "android_webview/browser/aw_browser_context.h"
+#include "components/cdm/browser/media_drm_storage_impl.h"
+#include "components/prefs/pref_service.h"
+#include "media/mojo/buildflags.h"
+
+namespace android_webview {
+
+namespace {
+
+#if BUILDFLAG(ENABLE_MOJO_CDM)
+void CreateOriginId(cdm::MediaDrmStorageImpl::OriginIdObtainedCB callback) {
+  std::move(callback).Run(true, base::UnguessableToken::Create());
+}
+
+void AllowEmptyOriginIdCB(base::OnceCallback<void(bool)> callback) {
+  // Since CreateOriginId() always returns a non-empty origin ID, we don't need
+  // to allow empty origin ID.
+  std::move(callback).Run(false);
+}
+
+void CreateMediaDrmStorage(
+    content::RenderFrameHost* render_frame_host,
+    mojo::PendingReceiver<::media::mojom::MediaDrmStorage> receiver) {
+  DCHECK(render_frame_host);
+
+  if (render_frame_host->GetLastCommittedOrigin().opaque()) {
+    DVLOG(1) << __func__ << ": Unique origin.";
+    return;
+  }
+
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(render_frame_host);
+  DCHECK(web_contents) << "WebContents not available.";
+
+  auto* aw_browser_context =
+      static_cast<AwBrowserContext*>(web_contents->GetBrowserContext());
+  DCHECK(aw_browser_context) << "AwBrowserContext not available.";
+
+  PrefService* pref_service = aw_browser_context->GetPrefService();
+  DCHECK(pref_service);
+
+  // The object will be deleted on connection error, or when the frame navigates
+  // away.
+  new cdm::MediaDrmStorageImpl(
+      render_frame_host, pref_service, base::BindRepeating(&CreateOriginId),
+      base::BindRepeating(&AllowEmptyOriginIdCB), std::move(receiver));
+}
+#endif  // BUILDFLAG(ENABLE_MOJO_CDM)
+
+}  // anonymous namespace
+
+void AwContentBrowserClient::BindMediaServiceReceiver(
+    content::RenderFrameHost* render_frame_host,
+    mojo::GenericPendingReceiver receiver) {
+#if BUILDFLAG(ENABLE_MOJO_CDM)
+  if (auto r = receiver.As<media::mojom::MediaDrmStorage>()) {
+    CreateMediaDrmStorage(render_frame_host, std::move(r));
+    return;
+  }
+#endif
+}
+
+}  // namespace android_webview
diff --git a/apps/browser_context_keyed_service_factories.cc b/apps/browser_context_keyed_service_factories.cc
index ef5700b..6f6e053d 100644
--- a/apps/browser_context_keyed_service_factories.cc
+++ b/apps/browser_context_keyed_service_factories.cc
@@ -4,6 +4,7 @@
 
 #include "apps/browser_context_keyed_service_factories.h"
 
+#include "apps/app_lifetime_monitor_factory.h"
 #include "apps/app_restore_service.h"
 #include "apps/app_restore_service_factory.h"
 #include "apps/saved_files_service.h"
@@ -13,6 +14,7 @@
 namespace apps {
 
 void EnsureBrowserContextKeyedServiceFactoriesBuilt() {
+  AppLifetimeMonitorFactory::GetInstance();
   AppRestoreServiceFactory::GetInstance();
 }
 
diff --git a/ash/accessibility/accessibility_controller_impl.cc b/ash/accessibility/accessibility_controller_impl.cc
index 9faa3e0..ec973c1 100644
--- a/ash/accessibility/accessibility_controller_impl.cc
+++ b/ash/accessibility/accessibility_controller_impl.cc
@@ -91,6 +91,8 @@
      &kDictationMenuIcon},
     {FeatureType::kFocusHighlight, prefs::kAccessibilityFocusHighlightEnabled,
      nullptr, /* conflicting_feature= */ FeatureType::kSpokenFeedback},
+    {FeatureType::kFloatingMenu, prefs::kAccessibilityFloatingMenuEnabled,
+     nullptr},
     {FeatureType::kFullscreenMagnifier,
      prefs::kAccessibilityScreenMagnifierEnabled,
      &kSystemMenuAccessibilityFullscreenMagnifierIcon},
@@ -586,6 +588,9 @@
       prefs::kAccessibilityDictationEnabled, false,
       user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
   registry->RegisterBooleanPref(
+      prefs::kAccessibilityFloatingMenuEnabled, false,
+      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
+  registry->RegisterBooleanPref(
       prefs::kAccessibilityFocusHighlightEnabled, false,
       user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
   registry->RegisterBooleanPref(
@@ -803,6 +808,11 @@
   return GetFeature(FeatureType::kFocusHighlight);
 }
 
+AccessibilityControllerImpl::Feature&
+AccessibilityControllerImpl::floating_menu() const {
+  return GetFeature(FeatureType::kFloatingMenu);
+}
+
 AccessibilityControllerImpl::FeatureWithDialog&
 AccessibilityControllerImpl::fullscreen_magnifier() const {
   return static_cast<FeatureWithDialog&>(
@@ -1506,7 +1516,7 @@
 }
 
 void AccessibilityControllerImpl::SetAutoclickMenuPosition(
-    AutoclickMenuPosition position) {
+    FloatingMenuPosition position) {
   if (!active_user_prefs_)
     return;
   active_user_prefs_->SetInteger(prefs::kAccessibilityAutoclickMenuPosition,
@@ -1515,9 +1525,9 @@
   Shell::Get()->autoclick_controller()->SetMenuPosition(position);
 }
 
-AutoclickMenuPosition AccessibilityControllerImpl::GetAutoclickMenuPosition() {
+FloatingMenuPosition AccessibilityControllerImpl::GetAutoclickMenuPosition() {
   DCHECK(active_user_prefs_);
-  return static_cast<AutoclickMenuPosition>(active_user_prefs_->GetInteger(
+  return static_cast<FloatingMenuPosition>(active_user_prefs_->GetInteger(
       prefs::kAccessibilityAutoclickMenuPosition));
 }
 
@@ -1766,6 +1776,8 @@
       break;
     case FeatureType::kDictation:
       break;
+    case FeatureType::kFloatingMenu:
+      break;
     case FeatureType::kFocusHighlight:
       UpdateAccessibilityHighlightingFromPrefs();
       break;
diff --git a/ash/accessibility/accessibility_controller_impl.h b/ash/accessibility/accessibility_controller_impl.h
index 11803bd..ea32f6c 100644
--- a/ash/accessibility/accessibility_controller_impl.h
+++ b/ash/accessibility/accessibility_controller_impl.h
@@ -59,6 +59,7 @@
     KCursorHighlight,
     kDictation,
     kFocusHighlight,
+    kFloatingMenu,
     kFullscreenMagnifier,
     kDockedMagnifier,
     kHighContrast,
@@ -168,6 +169,7 @@
   FeatureWithDialog& dictation() const;
   Feature& focus_highlight() const;
   FeatureWithDialog& fullscreen_magnifier() const;
+  Feature& floating_menu() const;
   FeatureWithDialog& docked_magnifier() const;
   FeatureWithDialog& high_contrast() const;
   Feature& large_cursor() const;
@@ -199,8 +201,8 @@
 
   void SetAutoclickEventType(AutoclickEventType event_type);
   AutoclickEventType GetAutoclickEventType();
-  void SetAutoclickMenuPosition(AutoclickMenuPosition position);
-  AutoclickMenuPosition GetAutoclickMenuPosition();
+  void SetAutoclickMenuPosition(FloatingMenuPosition position);
+  FloatingMenuPosition GetAutoclickMenuPosition();
   void RequestAutoclickScrollableBoundsForPoint(gfx::Point& point_in_screen);
 
   // Update the autoclick menu bounds if necessary. This may need to happen when
diff --git a/ash/autoclick/autoclick_controller.cc b/ash/autoclick/autoclick_controller.cc
index 88defea..7d63dd0 100644
--- a/ash/autoclick/autoclick_controller.cc
+++ b/ash/autoclick/autoclick_controller.cc
@@ -207,7 +207,7 @@
   UpdateRingSize();
 }
 
-void AutoclickController::SetMenuPosition(AutoclickMenuPosition menu_position) {
+void AutoclickController::SetMenuPosition(FloatingMenuPosition menu_position) {
   menu_position_ = menu_position;
   UpdateAutoclickMenuBoundsIfNeeded();
 }
diff --git a/ash/autoclick/autoclick_controller.h b/ash/autoclick/autoclick_controller.h
index 6c5a369..9d365e3 100644
--- a/ash/autoclick/autoclick_controller.h
+++ b/ash/autoclick/autoclick_controller.h
@@ -74,7 +74,7 @@
   void SetMovementThreshold(int movement_threshold);
 
   // Sets the menu position and updates the UI.
-  void SetMenuPosition(AutoclickMenuPosition menu_position);
+  void SetMenuPosition(FloatingMenuPosition menu_position);
 
   // Performs the given ScrollPadAction at the current scrolling point.
   void DoScrollAction(ScrollPadAction action);
@@ -166,7 +166,7 @@
   // manually, the position will be fixed regardless of language direction and
   // shelf position. This probably means adding a new AutoclickMenuPostion
   // enum for "system default".
-  AutoclickMenuPosition menu_position_ = kDefaultAutoclickMenuPosition;
+  FloatingMenuPosition menu_position_ = kDefaultAutoclickMenuPosition;
   int mouse_event_flags_ = ui::EF_NONE;
   // The target window is observed by AutoclickController for the duration
   // of a autoclick gesture.
diff --git a/ash/autoclick/autoclick_unittest.cc b/ash/autoclick/autoclick_unittest.cc
index af0ddcc..21c71cac4 100644
--- a/ash/autoclick/autoclick_unittest.cc
+++ b/ash/autoclick/autoclick_unittest.cc
@@ -829,12 +829,12 @@
   const struct {
     const std::string display_spec;
     float scale;
-    AutoclickMenuPosition position;
+    FloatingMenuPosition position;
   } kTestCases[] = {
-      {"800x600", 1.0f, AutoclickMenuPosition::kBottomRight},
-      {"1024x800*2.0", 2.0f, AutoclickMenuPosition::kBottomRight},
-      {"800x600", 1.0f, AutoclickMenuPosition::kTopLeft},
-      {"1024x800*2.0", 2.0f, AutoclickMenuPosition::kTopLeft},
+      {"800x600", 1.0f, FloatingMenuPosition::kBottomRight},
+      {"1024x800*2.0", 2.0f, FloatingMenuPosition::kBottomRight},
+      {"800x600", 1.0f, FloatingMenuPosition::kTopLeft},
+      {"1024x800*2.0", 2.0f, FloatingMenuPosition::kTopLeft},
   };
   for (const auto& test : kTestCases) {
     UpdateDisplay(test.display_spec);
@@ -916,7 +916,7 @@
   GetAutoclickController()->SetAutoclickEventType(
       AutoclickEventType::kNoAction);
   Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition(
-      AutoclickMenuPosition::kBottomRight);
+      FloatingMenuPosition::kBottomRight);
 
   int animation_delay = 5;
   int full_delay = UpdateAnimationDelayAndGetFullDelay(animation_delay);
@@ -987,7 +987,7 @@
   // Set up autoclick and the shelf.
   Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true);
   Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition(
-      AutoclickMenuPosition::kBottomRight);
+      FloatingMenuPosition::kBottomRight);
   Shelf* shelf = GetPrimaryShelf();
   shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
   EXPECT_EQ(shelf->GetVisibilityState(), SHELF_VISIBLE);
@@ -1039,7 +1039,7 @@
     // Set up autoclick and the shelf.
     Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true);
     Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition(
-        AutoclickMenuPosition::kBottomRight);
+        FloatingMenuPosition::kBottomRight);
     auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
     EXPECT_FALSE(unified_system_tray->IsBubbleShown());
     AutoclickMenuView* menu = GetAutoclickMenuView();
@@ -1304,7 +1304,7 @@
   GetAutoclickController()->SetEnabled(true, false /* do not show dialog */);
 
   Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition(
-      AutoclickMenuPosition::kBottomRight);
+      FloatingMenuPosition::kBottomRight);
   GetAutoclickController()->SetAutoclickEventType(AutoclickEventType::kScroll);
 
   ASSERT_TRUE(GetAutoclickScrollView());
@@ -1321,21 +1321,21 @@
 
   // Moving the autoclick menu around the screen moves the scroll bubble too.
   Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition(
-      AutoclickMenuPosition::kBottomLeft);
+      FloatingMenuPosition::kBottomLeft);
   scroll_bounds = GetAutoclickScrollView()->GetBoundsInScreen();
   menu_bounds = GetAutoclickMenuView()->GetBoundsInScreen();
   EXPECT_LT(menu_bounds.ManhattanInternalDistance(scroll_bounds),
             kScrollToMenuBoundsBuffer);
 
   Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition(
-      AutoclickMenuPosition::kTopLeft);
+      FloatingMenuPosition::kTopLeft);
   scroll_bounds = GetAutoclickScrollView()->GetBoundsInScreen();
   menu_bounds = GetAutoclickMenuView()->GetBoundsInScreen();
   EXPECT_LT(menu_bounds.ManhattanInternalDistance(scroll_bounds),
             kScrollToMenuBoundsBuffer);
 
   Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition(
-      AutoclickMenuPosition::kTopRight);
+      FloatingMenuPosition::kTopRight);
   scroll_bounds = GetAutoclickScrollView()->GetBoundsInScreen();
   menu_bounds = GetAutoclickMenuView()->GetBoundsInScreen();
   EXPECT_LT(menu_bounds.ManhattanInternalDistance(scroll_bounds),
@@ -1355,7 +1355,7 @@
   // Moving the bubble menu now does not change the scroll bubble's position,
   // it remains near its point.
   Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition(
-      AutoclickMenuPosition::kBottomRight);
+      FloatingMenuPosition::kBottomRight);
   EXPECT_EQ(GetAutoclickScrollView()->GetBoundsInScreen(), scroll_bounds);
 }
 
diff --git a/ash/display/display_manager_unittest.cc b/ash/display/display_manager_unittest.cc
index dfd2923..af1a98e4 100644
--- a/ash/display/display_manager_unittest.cc
+++ b/ash/display/display_manager_unittest.cc
@@ -386,8 +386,8 @@
 
 TEST_F(DisplayManagerTest, LayoutMorethanThreeDisplaysTest) {
   int64_t primary_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
-  display::DisplayIdList list = display::test::CreateDisplayIdListN(
-      3, primary_id, primary_id + 1, primary_id + 2);
+  display::DisplayIdList list =
+      display::test::CreateDisplayIdListN(primary_id, 3);
   {
     // Layout: [2]
     //         [1][P]
@@ -461,9 +461,7 @@
   }
 
   {
-    list = display::test::CreateDisplayIdListN(5, primary_id, primary_id + 1,
-                                               primary_id + 2, primary_id + 3,
-                                               primary_id + 4);
+    list = display::test::CreateDisplayIdListN(primary_id, 5);
     // Layout: [P][2]
     //      [3][4]
     //      [1]
@@ -523,9 +521,8 @@
     //                 |        | |        |
     //                 +--------+ +--------+
 
-    display::DisplayIdList list = display::test::CreateDisplayIdListN(
-        8, primary_id, primary_id + 1, primary_id + 2, primary_id + 3,
-        primary_id + 4, primary_id + 5, primary_id + 6, primary_id + 7);
+    display::DisplayIdList list =
+        display::test::CreateDisplayIdListN(primary_id, 8);
     display::DisplayLayoutBuilder builder(primary_id);
     builder.AddDisplayPlacement(list[1], primary_id,
                                 display::DisplayPlacement::BOTTOM, 50);
@@ -633,8 +630,8 @@
     // +------+--+----+
     //
 
-    display::DisplayIdList list = display::test::CreateDisplayIdListN(
-        4, primary_id, primary_id + 1, primary_id + 2, primary_id + 3);
+    display::DisplayIdList list =
+        display::test::CreateDisplayIdListN(primary_id, 4);
     display::DisplayLayoutBuilder builder(primary_id);
     builder.AddDisplayPlacement(list[1], primary_id,
                                 display::DisplayPlacement::BOTTOM, 0);
@@ -702,8 +699,8 @@
     // |         |
     // +---------+
 
-    display::DisplayIdList list = display::test::CreateDisplayIdListN(
-        3, primary_id, primary_id + 1, primary_id + 2);
+    display::DisplayIdList list =
+        display::test::CreateDisplayIdListN(primary_id, 3);
     display::DisplayLayoutBuilder builder(primary_id);
     builder.AddDisplayPlacement(list[1], primary_id,
                                 display::DisplayPlacement::LEFT, 0);
@@ -753,8 +750,8 @@
     //           +---------+
     //
 
-    display::DisplayIdList list = display::test::CreateDisplayIdListN(
-        3, primary_id, primary_id + 1, primary_id + 2);
+    display::DisplayIdList list =
+        display::test::CreateDisplayIdListN(primary_id, 3);
     display::DisplayLayoutBuilder builder(primary_id);
     builder.AddDisplayPlacement(list[1], primary_id,
                                 display::DisplayPlacement::TOP, 0);
@@ -804,8 +801,8 @@
   //
 
   int64_t primary_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
-  display::DisplayIdList list = display::test::CreateDisplayIdListN(
-      4, primary_id, primary_id + 1, primary_id + 2, primary_id + 3);
+  display::DisplayIdList list =
+      display::test::CreateDisplayIdListN(primary_id, 4);
   display::DisplayLayoutBuilder builder(primary_id);
   builder.AddDisplayPlacement(list[1], primary_id,
                               display::DisplayPlacement::TOP, -110);
@@ -866,9 +863,8 @@
   //
 
   int64_t primary_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
-  display::DisplayIdList list = display::test::CreateDisplayIdListN(
-      5, primary_id, primary_id + 1, primary_id + 2, primary_id + 3,
-      primary_id + 4);
+  display::DisplayIdList list =
+      display::test::CreateDisplayIdListN(primary_id, 5);
   display::DisplayLayoutBuilder builder(primary_id);
   builder.AddDisplayPlacement(list[1], primary_id,
                               display::DisplayPlacement::TOP, -250);
@@ -949,9 +945,8 @@
   //
 
   int64_t primary_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
-  display::DisplayIdList list = display::test::CreateDisplayIdListN(
-      6, primary_id, primary_id + 1, primary_id + 2, primary_id + 3,
-      primary_id + 4, primary_id + 5);
+  display::DisplayIdList list =
+      display::test::CreateDisplayIdListN(primary_id, 6);
   display::DisplayLayoutBuilder builder(primary_id);
   builder.AddDisplayPlacement(list[1], primary_id,
                               display::DisplayPlacement::TOP, -250);
@@ -1670,7 +1665,7 @@
       display_manager()->GetDisplayInfo(secondary_id);
 
   // An id which is different from primary and secondary.
-  const int64_t third_id = secondary_id + 1;
+  const int64_t third_id = display::GetNextSynthesizedDisplayId(secondary_id);
 
   display::ManagedDisplayInfo third_info =
       display::CreateDisplayInfo(third_id, gfx::Rect(0, 0, 600, 600));
@@ -2452,11 +2447,11 @@
   display::test::DisplayManagerTestApi display_manager_test(display_manager());
   EXPECT_EQ(gfx::Size(400, 500),
             display_manager_test.GetSecondaryDisplay().size());
-  EXPECT_EQ(
-      gfx::Size(500, 300),
-      display_manager()
-          ->GetDisplayForId(display_manager_test.GetSecondaryDisplay().id() + 1)
-          .size());
+  EXPECT_EQ(gfx::Size(500, 300),
+            display_manager()
+                ->GetDisplayForId(display::GetNextSynthesizedDisplayId(
+                    display_manager_test.GetSecondaryDisplay().id()))
+                .size());
 }
 
 TEST_F(DisplayManagerTest, UnifiedDesktopWithHardwareMirroring) {
diff --git a/ash/display/display_move_window_util_unittest.cc b/ash/display/display_move_window_util_unittest.cc
index 3a08bd0..0835ca0 100644
--- a/ash/display/display_move_window_util_unittest.cc
+++ b/ash/display/display_move_window_util_unittest.cc
@@ -158,8 +158,8 @@
   // Layout:
   // [3][2]
   // [1][p]
-  display::DisplayIdList list = display::test::CreateDisplayIdListN(
-      4, primary_id, primary_id + 1, primary_id + 2, primary_id + 3);
+  display::DisplayIdList list =
+      display::test::CreateDisplayIdListN(primary_id, 4);
   display::DisplayLayoutBuilder builder(primary_id);
   builder.AddDisplayPlacement(list[1], primary_id,
                               display::DisplayPlacement::LEFT, 0);
diff --git a/ash/display/display_prefs_unittest.cc b/ash/display/display_prefs_unittest.cc
index 120ffd9..d1a4d6a 100644
--- a/ash/display/display_prefs_unittest.cc
+++ b/ash/display/display_prefs_unittest.cc
@@ -42,6 +42,7 @@
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/manager/display_manager_utilities.h"
 #include "ui/display/manager/json_converter.h"
+#include "ui/display/manager/managed_display_info.h"
 #include "ui/display/manager/test/touch_device_manager_test_api.h"
 #include "ui/display/screen.h"
 #include "ui/display/test/display_manager_test_api.h"
@@ -252,8 +253,8 @@
   UpdateDisplay("100x100,200x200");
 
   display::DisplayIdList list = display_manager()->GetCurrentDisplayIdList();
-  display::DisplayIdList dummy_list =
-      display::test::CreateDisplayIdList2(list[0], list[1] + 1);
+  display::DisplayIdList dummy_list = display::test::CreateDisplayIdList2(
+      list[0], display::GetNextSynthesizedDisplayId(list[1]));
   ASSERT_NE(list[0], dummy_list[1]);
 
   StoreDisplayLayoutPrefForList(list, display::DisplayPlacement::TOP, 20);
@@ -280,15 +281,15 @@
   // The new layout overrides old layout.
   // Inverted one of for specified pair (id1, id2).  Not used for the list
   // (id1, dummy_id) since dummy_id is not connected right now.
-  EXPECT_EQ("id=2200000001, parent=2200000000, top, 20",
+  EXPECT_EQ("id=2200000257, parent=2200000000, top, 20",
             Shell::Get()
                 ->display_manager()
                 ->GetCurrentDisplayLayout()
                 .placement_list[0]
                 .ToString());
-  EXPECT_EQ("id=2200000001, parent=2200000000, top, 20",
+  EXPECT_EQ("id=2200000257, parent=2200000000, top, 20",
             GetRegisteredDisplayPlacementStr(list));
-  EXPECT_EQ("id=2200000002, parent=2200000000, left, 30",
+  EXPECT_EQ("id=2200000258, parent=2200000000, left, 30",
             GetRegisteredDisplayPlacementStr(dummy_list));
 }
 
@@ -304,7 +305,7 @@
   UpdateDisplay("200x200*2, 400x300#400x400|300x200*1.25");
   display::test::DisplayManagerTestApi display_manager_test(display_manager());
   int64_t id2 = display_manager_test.GetSecondaryDisplay().id();
-  int64_t dummy_id = id2 + 1;
+  int64_t dummy_id = display::GetNextSynthesizedDisplayId(id2);
   ASSERT_NE(id1, dummy_id);
 
   LoggedInAsUser();
@@ -547,8 +548,8 @@
 
   // Set new display's selected resolution.
   display_manager()->RegisterDisplayProperty(
-      id2 + 1, display::Display::ROTATE_0, nullptr, gfx::Size(500, 400), 1.0f,
-      1.0f, 60.f, false);
+      display::GetNextSynthesizedDisplayId(id2), display::Display::ROTATE_0,
+      nullptr, gfx::Size(500, 400), 1.0f, 1.0f, 60.f, false);
 
   UpdateDisplay("200x200*2, 600x500#600x500|500x400");
   EXPECT_FALSE(display_manager()->IsInMirrorMode());
@@ -573,8 +574,8 @@
 
   // Set yet another new display's selected resolution.
   display_manager()->RegisterDisplayProperty(
-      id2 + 1, display::Display::ROTATE_0, nullptr, gfx::Size(500, 400), 1.0f,
-      1.0f, 60.f, false);
+      display::GetNextSynthesizedDisplayId(id2), display::Display::ROTATE_0,
+      nullptr, gfx::Size(500, 400), 1.0f, 1.0f, 60.f, false);
   // Disconnect 2nd display first to generate new id for external display.
   UpdateDisplay("200x200*2");
   UpdateDisplay("200x200*2, 500x400#600x500|500x400%60.0f");
@@ -1167,8 +1168,7 @@
 TEST_F(DisplayPrefsTest, RestoreThreeDisplays) {
   LoggedInAsUser();
   int64_t id1 = display::Screen::GetScreen()->GetPrimaryDisplay().id();
-  display::DisplayIdList list =
-      display::test::CreateDisplayIdListN(3, id1, id1 + 1, id1 + 2);
+  display::DisplayIdList list = display::test::CreateDisplayIdListN(id1, 3);
 
   display::DisplayLayoutBuilder builder(list[0]);
   builder.AddDisplayPlacement(list[1], list[0], display::DisplayPlacement::LEFT,
diff --git a/ash/display/window_tree_host_manager_unittest.cc b/ash/display/window_tree_host_manager_unittest.cc
index f3c4038d..91c660df0 100644
--- a/ash/display/window_tree_host_manager_unittest.cc
+++ b/ash/display/window_tree_host_manager_unittest.cc
@@ -752,7 +752,7 @@
   EXPECT_EQ("200,0 300x300", secondary_display.bounds().ToString());
   EXPECT_EQ(gfx::Rect(200, 0, 300, shelf_inset_second).ToString(),
             secondary_display.work_area().ToString());
-  EXPECT_EQ("id=2200000001, parent=2200000000, right, 50",
+  EXPECT_EQ("id=2200000257, parent=2200000000, right, 50",
             display_manager()
                 ->GetCurrentDisplayLayout()
                 .placement_list[0]
@@ -777,7 +777,7 @@
   const display::DisplayLayout& inverted_layout =
       display_manager()->GetCurrentDisplayLayout();
 
-  EXPECT_EQ("id=2200000000, parent=2200000001, left, -50",
+  EXPECT_EQ("id=2200000000, parent=2200000257, left, -50",
             inverted_layout.placement_list[0].ToString());
   // Test if the bounds are correctly swapped.
   display::Display swapped_primary =
diff --git a/ash/public/cpp/accessibility_controller_enums.h b/ash/public/cpp/accessibility_controller_enums.h
index 1d9f39c69a..f3c21f0 100644
--- a/ash/public/cpp/accessibility_controller_enums.h
+++ b/ash/public/cpp/accessibility_controller_enums.h
@@ -122,10 +122,10 @@
   kMaxValue = kScroll
 };
 
-// The Automatic Clicks feature's on-screen menu display location. These values
-// are written to prefs so they should not be changed. New values should be
-// added at the end.
-enum class AutoclickMenuPosition {
+// Display location of the on-screen floating menus used by accessibility features(e.g. the
+// Automatic Clicks) . These values are written to prefs so they should not be changed. New values
+// should be added at the end.
+enum class FloatingMenuPosition {
   // The bottom right of the screen.
   kBottomRight,
 
diff --git a/ash/public/cpp/ash_constants.h b/ash/public/cpp/ash_constants.h
index a1c377f..f6a9fde 100644
--- a/ash/public/cpp/ash_constants.h
+++ b/ash/public/cpp/ash_constants.h
@@ -57,8 +57,8 @@
 constexpr int kDefaultAutoclickMovementThreshold = 20;
 
 // The default automatic click menu position.
-constexpr AutoclickMenuPosition kDefaultAutoclickMenuPosition =
-    AutoclickMenuPosition::kSystemDefault;
+constexpr FloatingMenuPosition kDefaultAutoclickMenuPosition =
+    FloatingMenuPosition::kSystemDefault;
 
 // The default frame color.
 constexpr SkColor kDefaultFrameColor = SkColorSetRGB(0xFD, 0xFE, 0xFF);
diff --git a/ash/public/cpp/ash_pref_names.cc b/ash/public/cpp/ash_pref_names.cc
index 428f3aad..86b1c4b9 100644
--- a/ash/public/cpp/ash_pref_names.cc
+++ b/ash/public/cpp/ash_pref_names.cc
@@ -77,6 +77,9 @@
 // A boolean pref which determines whether cursor highlighting is enabled.
 const char kAccessibilityCursorHighlightEnabled[] =
     "settings.a11y.cursor_highlight";
+// A boolean pref which determines whether floating accessibility menu is
+// enabled.
+const char kAccessibilityFloatingMenuEnabled[] = "settings.a11y.floating_menu";
 // A boolean pref which determines whether focus highlighting is enabled.
 const char kAccessibilityFocusHighlightEnabled[] =
     "settings.a11y.focus_highlight";
diff --git a/ash/public/cpp/ash_pref_names.h b/ash/public/cpp/ash_pref_names.h
index 1d2bb4d..020e4b43 100644
--- a/ash/public/cpp/ash_pref_names.h
+++ b/ash/public/cpp/ash_pref_names.h
@@ -31,6 +31,7 @@
 ASH_PUBLIC_EXPORT extern const char kAccessibilityAutoclickMenuPosition[];
 ASH_PUBLIC_EXPORT extern const char kAccessibilityCaretHighlightEnabled[];
 ASH_PUBLIC_EXPORT extern const char kAccessibilityCursorHighlightEnabled[];
+ASH_PUBLIC_EXPORT extern const char kAccessibilityFloatingMenuEnabled[];
 ASH_PUBLIC_EXPORT extern const char kAccessibilityFocusHighlightEnabled[];
 ASH_PUBLIC_EXPORT extern const char kAccessibilitySelectToSpeakEnabled[];
 ASH_PUBLIC_EXPORT extern const char kAccessibilitySwitchAccessEnabled[];
diff --git a/ash/public/cpp/shelf_item_delegate.h b/ash/public/cpp/shelf_item_delegate.h
index 21f66393..437878c 100644
--- a/ash/public/cpp/shelf_item_delegate.h
+++ b/ash/public/cpp/shelf_item_delegate.h
@@ -14,6 +14,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "ui/events/event.h"
+#include "ui/gfx/image/image_skia.h"
 
 class AppWindowLauncherItemController;
 
diff --git a/ash/shelf/login_shelf_view.cc b/ash/shelf/login_shelf_view.cc
index 98a6f99..276005c8 100644
--- a/ash/shelf/login_shelf_view.cc
+++ b/ash/shelf/login_shelf_view.cc
@@ -38,6 +38,7 @@
 #include "skia/ext/image_operations.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/models/menu_model.h"
 #include "ui/base/models/simple_menu_model.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
@@ -316,7 +317,8 @@
       gfx::ImageSkia icon = gfx::ImageSkiaOperations::CreateResizedImage(
           kiosk_apps_[i].icon, skia::ImageOperations::RESIZE_GOOD,
           kAppIconSize);
-      AddItemWithIcon(i, kiosk_apps_[i].name, icon);
+      AddItemWithIcon(i, kiosk_apps_[i].name,
+                      ui::ImageModel::FromImageSkia(icon));
     }
   }
 
diff --git a/ash/shelf/scrollable_shelf_view.cc b/ash/shelf/scrollable_shelf_view.cc
index 5da91558..5c321ee6d 100644
--- a/ash/shelf/scrollable_shelf_view.cc
+++ b/ash/shelf/scrollable_shelf_view.cc
@@ -1428,12 +1428,17 @@
   const gfx::Rect screen_bounds =
       use_target_bounds ? GetShelf()->hotseat_widget()->GetTargetBounds()
                         : GetBoundsInScreen();
-  int before_padding = gap - GetShelf()->PrimaryAxisValue(
-                                 screen_bounds.x() - display_bounds.x(),
-                                 screen_bounds.y() - display_bounds.y());
+  int before_padding =
+      gap - GetShelf()->PrimaryAxisValue(
+                ShouldAdaptToRTL()
+                    ? display_bounds.right() - screen_bounds.right()
+                    : screen_bounds.x() - display_bounds.x(),
+                screen_bounds.y() - display_bounds.y());
   int after_padding =
       gap - GetShelf()->PrimaryAxisValue(
-                display_bounds.right() - screen_bounds.right(),
+                ShouldAdaptToRTL()
+                    ? screen_bounds.x() - display_bounds.x()
+                    : display_bounds.right() - screen_bounds.right(),
                 display_bounds.bottom() - screen_bounds.bottom());
 
   before_padding -= base_padding_;
diff --git a/ash/shelf/scrollable_shelf_view.h b/ash/shelf/scrollable_shelf_view.h
index 06f1b41..0527af4 100644
--- a/ash/shelf/scrollable_shelf_view.h
+++ b/ash/shelf/scrollable_shelf_view.h
@@ -449,8 +449,7 @@
   ScrollArrowView* right_arrow_ = nullptr;
   ShelfContainerView* shelf_container_view_ = nullptr;
 
-  // Available space to accommodate child views. It is mirrored for horizontal
-  // shelf under RTL.
+  // Available space to accommodate child views.
   gfx::Rect available_space_;
 
   ShelfView* shelf_view_ = nullptr;
diff --git a/ash/shelf/shelf_application_menu_model.cc b/ash/shelf/shelf_application_menu_model.cc
index c06c51ef..7bf72a01 100644
--- a/ash/shelf/shelf_application_menu_model.cc
+++ b/ash/shelf/shelf_application_menu_model.cc
@@ -11,6 +11,7 @@
 #include "ash/public/cpp/shelf_item_delegate.h"
 #include "ash/shell.h"
 #include "base/metrics/histogram_macros.h"
+#include "ui/base/models/image_model.h"
 #include "ui/display/types/display_constants.h"
 
 namespace ash {
@@ -22,7 +23,8 @@
     : ui::SimpleMenuModel(this), delegate_(delegate) {
   AddTitle(title);
   for (size_t i = 0; i < items.size(); i++)
-    AddItemWithIcon(i, items[i].first, items[i].second);
+    AddItemWithIcon(i, items[i].first,
+                    ui::ImageModel::FromImageSkia(items[i].second));
   AddSeparator(ui::SPACING_SEPARATOR);
   DCHECK_EQ(GetItemCount(), int{items.size() + 2}) << "Update metrics |- 2|";
 }
diff --git a/ash/shelf/shelf_application_menu_model.h b/ash/shelf/shelf_application_menu_model.h
index 9119b7e..8a62bb8e 100644
--- a/ash/shelf/shelf_application_menu_model.h
+++ b/ash/shelf/shelf_application_menu_model.h
@@ -12,6 +12,7 @@
 #include "ash/ash_export.h"
 #include "base/macros.h"
 #include "ui/base/models/simple_menu_model.h"
+#include "ui/gfx/image/image_skia.h"
 
 namespace ash {
 
diff --git a/ash/shelf/shelf_context_menu_model.cc b/ash/shelf/shelf_context_menu_model.cc
index 57a6280..5271bba 100644
--- a/ash/shelf/shelf_context_menu_model.cc
+++ b/ash/shelf/shelf_context_menu_model.cc
@@ -26,6 +26,7 @@
 #include "base/metrics/user_metrics.h"
 #include "base/numerics/safe_conversions.h"
 #include "components/prefs/pref_service.h"
+#include "ui/base/models/image_model.h"
 
 namespace ash {
 
@@ -144,7 +145,8 @@
                          : IDS_ASH_SHELF_CONTEXT_MENU_AUTO_HIDE;
     AddItemWithStringIdAndIcon(
         MENU_AUTO_HIDE, string_id,
-        is_autohide_set ? kAlwaysShowShelfIcon : kAutoHideIcon);
+        ui::ImageModel::FromVectorIcon(is_autohide_set ? kAlwaysShowShelfIcon
+                                                       : kAutoHideIcon));
   }
 
   // Only allow shelf alignment modifications by the owner or user. In tablet
@@ -163,14 +165,16 @@
     alignment_submenu_->AddRadioItemWithStringId(
         MENU_ALIGNMENT_RIGHT, IDS_ASH_SHELF_CONTEXT_MENU_ALIGN_RIGHT, group);
 
-    AddSubMenuWithStringIdAndIcon(MENU_ALIGNMENT_MENU,
-                                  IDS_ASH_SHELF_CONTEXT_MENU_POSITION,
-                                  alignment_submenu_.get(), kShelfPositionIcon);
+    AddSubMenuWithStringIdAndIcon(
+        MENU_ALIGNMENT_MENU, IDS_ASH_SHELF_CONTEXT_MENU_POSITION,
+        alignment_submenu_.get(),
+        ui::ImageModel::FromVectorIcon(kShelfPositionIcon));
   }
 
   if (Shell::Get()->wallpaper_controller()->CanOpenWallpaperPicker()) {
     AddItemWithStringIdAndIcon(MENU_CHANGE_WALLPAPER,
-                               IDS_AURA_SET_DESKTOP_WALLPAPER, kWallpaperIcon);
+                               IDS_AURA_SET_DESKTOP_WALLPAPER,
+                               ui::ImageModel::FromVectorIcon(kWallpaperIcon));
   }
 }
 
diff --git a/ash/shelf/shelf_window_watcher_item_delegate.cc b/ash/shelf/shelf_window_watcher_item_delegate.cc
index 0c5b855..cbe362e 100644
--- a/ash/shelf/shelf_window_watcher_item_delegate.cc
+++ b/ash/shelf/shelf_window_watcher_item_delegate.cc
@@ -16,6 +16,7 @@
 #include "components/strings/grit/components_strings.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
+#include "ui/base/models/image_model.h"
 #include "ui/events/types/event_type.h"
 #include "ui/views/vector_icons.h"
 #include "ui/wm/core/window_animations.h"
@@ -63,8 +64,9 @@
     GetContextMenuCallback callback) {
   auto menu = std::make_unique<ShelfContextMenuModel>(this, display_id);
   // Show a default context menu with just an extra close item.
-  menu->AddItemWithStringIdAndIcon(kCloseCommandId, IDS_CLOSE,
-                                   views::kCloseIcon);
+  menu->AddItemWithStringIdAndIcon(
+      kCloseCommandId, IDS_CLOSE,
+      ui::ImageModel::FromVectorIcon(views::kCloseIcon));
   std::move(callback).Run(std::move(menu));
 }
 
diff --git a/ash/system/accessibility/autoclick_menu_bubble_controller.cc b/ash/system/accessibility/autoclick_menu_bubble_controller.cc
index 6923f12..1d0e177 100644
--- a/ash/system/accessibility/autoclick_menu_bubble_controller.cc
+++ b/ash/system/accessibility/autoclick_menu_bubble_controller.cc
@@ -29,34 +29,34 @@
 const int kAutoclickMenuWidth = 369;
 const int kAutoclickMenuHeight = 64;
 
-AutoclickMenuPosition DefaultSystemPosition() {
-  return base::i18n::IsRTL() ? AutoclickMenuPosition::kBottomLeft
-                             : AutoclickMenuPosition::kBottomRight;
+FloatingMenuPosition DefaultSystemPosition() {
+  return base::i18n::IsRTL() ? FloatingMenuPosition::kBottomLeft
+                             : FloatingMenuPosition::kBottomRight;
 }
 
 views::BubbleBorder::Arrow GetScrollAnchorAlignmentForPosition(
-    AutoclickMenuPosition position) {
+    FloatingMenuPosition position) {
   // If this is the default system position, pick the position based on the
   // language direction.
-  if (position == AutoclickMenuPosition::kSystemDefault) {
+  if (position == FloatingMenuPosition::kSystemDefault) {
     position = DefaultSystemPosition();
   }
   // Mirror arrow in RTL languages so that it always stays near the screen
   // edge.
   switch (position) {
-    case AutoclickMenuPosition::kBottomLeft:
+    case FloatingMenuPosition::kBottomLeft:
       return base::i18n::IsRTL() ? views::BubbleBorder::Arrow::TOP_RIGHT
                                  : views::BubbleBorder::Arrow::TOP_LEFT;
-    case AutoclickMenuPosition::kTopLeft:
+    case FloatingMenuPosition::kTopLeft:
       return base::i18n::IsRTL() ? views::BubbleBorder::Arrow::BOTTOM_RIGHT
                                  : views::BubbleBorder::Arrow::BOTTOM_LEFT;
-    case AutoclickMenuPosition::kBottomRight:
+    case FloatingMenuPosition::kBottomRight:
       return base::i18n::IsRTL() ? views::BubbleBorder::Arrow::TOP_LEFT
                                  : views::BubbleBorder::Arrow::TOP_RIGHT;
-    case AutoclickMenuPosition::kTopRight:
+    case FloatingMenuPosition::kTopRight:
       return base::i18n::IsRTL() ? views::BubbleBorder::Arrow::BOTTOM_LEFT
                                  : views::BubbleBorder::Arrow::BOTTOM_RIGHT;
-    case AutoclickMenuPosition::kSystemDefault:
+    case FloatingMenuPosition::kSystemDefault:
       // It's not possible for position to be kSystemDefault here because we've
       // set it via DefaultSystemPosition() above if it was kSystemDefault.
       NOTREACHED();
@@ -102,21 +102,21 @@
 }
 
 void AutoclickMenuBubbleController::SetPosition(
-    AutoclickMenuPosition new_position) {
+    FloatingMenuPosition new_position) {
   if (!menu_view_ || !bubble_view_ || !bubble_widget_)
     return;
 
   // Update the menu view's UX if the position has changed, or if it's not the
   // default position (because that can change with language direction).
   if (position_ != new_position ||
-      new_position == AutoclickMenuPosition::kSystemDefault) {
+      new_position == FloatingMenuPosition::kSystemDefault) {
     menu_view_->UpdatePosition(new_position);
   }
   position_ = new_position;
 
   // If this is the default system position, pick the position based on the
   // language direction.
-  if (new_position == AutoclickMenuPosition::kSystemDefault)
+  if (new_position == FloatingMenuPosition::kSystemDefault)
     new_position = DefaultSystemPosition();
 
   // Calculates the ideal bounds.
@@ -127,17 +127,17 @@
       WorkAreaInsets::ForWindow(window)->user_work_area_bounds();
   gfx::Rect new_bounds;
   switch (new_position) {
-    case AutoclickMenuPosition::kBottomRight:
+    case FloatingMenuPosition::kBottomRight:
       new_bounds = gfx::Rect(work_area.right() - kAutoclickMenuWidth,
                              work_area.bottom() - kAutoclickMenuHeight,
                              kAutoclickMenuWidth, kAutoclickMenuHeight);
       break;
-    case AutoclickMenuPosition::kBottomLeft:
+    case FloatingMenuPosition::kBottomLeft:
       new_bounds =
           gfx::Rect(work_area.x(), work_area.bottom() - kAutoclickMenuHeight,
                     kAutoclickMenuWidth, kAutoclickMenuHeight);
       break;
-    case AutoclickMenuPosition::kTopLeft:
+    case FloatingMenuPosition::kTopLeft:
       // Because there is no inset at the top of the widget, add
       // 2 * kCollisionWindowWorkAreaInsetsDp to the top of the work area.
       // to ensure correct padding.
@@ -145,7 +145,7 @@
           work_area.x(), work_area.y() + 2 * kCollisionWindowWorkAreaInsetsDp,
           kAutoclickMenuWidth, kAutoclickMenuHeight);
       break;
-    case AutoclickMenuPosition::kTopRight:
+    case FloatingMenuPosition::kTopRight:
       // Because there is no inset at the top of the widget, add
       // 2 * kCollisionWindowWorkAreaInsetsDp to the top of the work area.
       // to ensure correct padding.
@@ -154,7 +154,7 @@
                     work_area.y() + 2 * kCollisionWindowWorkAreaInsetsDp,
                     kAutoclickMenuWidth, kAutoclickMenuHeight);
       break;
-    case AutoclickMenuPosition::kSystemDefault:
+    case FloatingMenuPosition::kSystemDefault:
       return;
   }
 
@@ -200,7 +200,7 @@
 }
 
 void AutoclickMenuBubbleController::ShowBubble(AutoclickEventType type,
-                                               AutoclickMenuPosition position) {
+                                               FloatingMenuPosition position) {
   // Ignore if bubble widget already exists.
   if (bubble_widget_)
     return;
@@ -319,7 +319,7 @@
 void AutoclickMenuBubbleController::OnLocaleChanged() {
   // Layout update is needed when language changes between LTR and RTL, if the
   // position is the system default.
-  if (position_ == AutoclickMenuPosition::kSystemDefault)
+  if (position_ == FloatingMenuPosition::kSystemDefault)
     SetPosition(position_);
 }
 
diff --git a/ash/system/accessibility/autoclick_menu_bubble_controller.h b/ash/system/accessibility/autoclick_menu_bubble_controller.h
index 07b5561..48ef33e 100644
--- a/ash/system/accessibility/autoclick_menu_bubble_controller.h
+++ b/ash/system/accessibility/autoclick_menu_bubble_controller.h
@@ -32,15 +32,14 @@
   void SetEventType(AutoclickEventType type);
 
   // Sets the menu's position on the screen.
-  void SetPosition(AutoclickMenuPosition position);
+  void SetPosition(FloatingMenuPosition position);
 
   // Set the scroll menu's position on the screen. The rect is the bounds of
   // the scrollable area, and the point is the user-selected scroll point.
   void SetScrollPosition(gfx::Rect scroll_bounds_in_dips,
                          const gfx::Point& scroll_point_in_dips);
 
-  void ShowBubble(AutoclickEventType event_type,
-                  AutoclickMenuPosition position);
+  void ShowBubble(AutoclickEventType event_type, FloatingMenuPosition position);
 
   void CloseBubble();
 
@@ -73,7 +72,7 @@
   // Owned by views hierarchy.
   TrayBubbleView* bubble_view_ = nullptr;
   AutoclickMenuView* menu_view_ = nullptr;
-  AutoclickMenuPosition position_ = kDefaultAutoclickMenuPosition;
+  FloatingMenuPosition position_ = kDefaultAutoclickMenuPosition;
 
   views::Widget* bubble_widget_ = nullptr;
 
diff --git a/ash/system/accessibility/autoclick_menu_bubble_controller_unittest.cc b/ash/system/accessibility/autoclick_menu_bubble_controller_unittest.cc
index ba0acac..8de0a231 100644
--- a/ash/system/accessibility/autoclick_menu_bubble_controller_unittest.cc
+++ b/ash/system/accessibility/autoclick_menu_bubble_controller_unittest.cc
@@ -180,7 +180,7 @@
       Shell::Get()->accessibility_controller();
 
   // Set to a known position for than the first event in kTestCases.
-  controller->SetAutoclickMenuPosition(AutoclickMenuPosition::kTopRight);
+  controller->SetAutoclickMenuPosition(FloatingMenuPosition::kTopRight);
 
   // Get the full root window bounds to test the position.
   gfx::Rect window_bounds = Shell::GetPrimaryRootWindow()->bounds();
@@ -188,14 +188,14 @@
   // Test cases rotate clockwise.
   const struct {
     gfx::Point expected_location;
-    AutoclickMenuPosition expected_position;
+    FloatingMenuPosition expected_position;
   } kTestCases[] = {
       {gfx::Point(window_bounds.width(), window_bounds.height()),
-       AutoclickMenuPosition::kBottomRight},
+       FloatingMenuPosition::kBottomRight},
       {gfx::Point(0, window_bounds.height()),
-       AutoclickMenuPosition::kBottomLeft},
-      {gfx::Point(0, 0), AutoclickMenuPosition::kTopLeft},
-      {gfx::Point(window_bounds.width(), 0), AutoclickMenuPosition::kTopRight},
+       FloatingMenuPosition::kBottomLeft},
+      {gfx::Point(0, 0), FloatingMenuPosition::kTopLeft},
+      {gfx::Point(window_bounds.width(), 0), FloatingMenuPosition::kTopRight},
   };
 
   // Find the autoclick menu position button.
@@ -277,7 +277,7 @@
 
     // When the menu is in the top right, the scroll view should be directly
     // under it and along the right side of the screen.
-    controller->SetAutoclickMenuPosition(AutoclickMenuPosition::kTopRight);
+    controller->SetAutoclickMenuPosition(FloatingMenuPosition::kTopRight);
     EXPECT_LT(GetScrollViewBounds().ManhattanDistanceToPoint(
                   GetMenuViewBounds().bottom_center()),
               kMenuViewBoundsBuffer);
@@ -285,7 +285,7 @@
 
     // When the menu is in the bottom right, the scroll view is directly above
     // it and along the right side of the screen.
-    controller->SetAutoclickMenuPosition(AutoclickMenuPosition::kBottomRight);
+    controller->SetAutoclickMenuPosition(FloatingMenuPosition::kBottomRight);
     EXPECT_LT(GetScrollViewBounds().ManhattanDistanceToPoint(
                   GetMenuViewBounds().top_center()),
               kMenuViewBoundsBuffer);
@@ -293,7 +293,7 @@
 
     // When the menu is on the bottom left, the scroll view is directly above it
     // and along the left side of the screen.
-    controller->SetAutoclickMenuPosition(AutoclickMenuPosition::kBottomLeft);
+    controller->SetAutoclickMenuPosition(FloatingMenuPosition::kBottomLeft);
     EXPECT_LT(GetScrollViewBounds().ManhattanDistanceToPoint(
                   GetMenuViewBounds().top_center()),
               kMenuViewBoundsBuffer);
@@ -301,7 +301,7 @@
 
     // When the menu is on the top left, the scroll view is directly below it
     // and along the left side of the screen.
-    controller->SetAutoclickMenuPosition(AutoclickMenuPosition::kTopLeft);
+    controller->SetAutoclickMenuPosition(FloatingMenuPosition::kTopLeft);
     EXPECT_LT(GetScrollViewBounds().ManhattanDistanceToPoint(
                   GetMenuViewBounds().bottom_center()),
               kMenuViewBoundsBuffer);
@@ -333,7 +333,7 @@
     UpdateDisplay(test.display_spec);
     base::i18n::SetRTLForTesting(test.is_RTL);
     gfx::Rect scroll_bounds = test.scroll_bounds;
-    controller->SetAutoclickMenuPosition(AutoclickMenuPosition::kTopRight);
+    controller->SetAutoclickMenuPosition(FloatingMenuPosition::kTopRight);
 
     // Start with a point no where near the autoclick menu.
     gfx::Point point = gfx::Point(400, 400);
@@ -355,7 +355,7 @@
     // Moving the autoclick bubble doesn't impact the scroll bubble once it
     // has been manually set.
     gfx::Rect bubble_bounds = GetScrollViewBounds();
-    controller->SetAutoclickMenuPosition(AutoclickMenuPosition::kBottomRight);
+    controller->SetAutoclickMenuPosition(FloatingMenuPosition::kBottomRight);
     EXPECT_EQ(bubble_bounds, GetScrollViewBounds());
 
     // If we position it by the edge of the screen, it should stay on-screen,
diff --git a/ash/system/accessibility/autoclick_menu_view.cc b/ash/system/accessibility/autoclick_menu_view.cc
index e02b04b..e9c21fd 100644
--- a/ash/system/accessibility/autoclick_menu_view.cc
+++ b/ash/system/accessibility/autoclick_menu_view.cc
@@ -36,7 +36,7 @@
 }  // namespace
 
 AutoclickMenuView::AutoclickMenuView(AutoclickEventType type,
-                                     AutoclickMenuPosition position)
+                                     FloatingMenuPosition position)
     : left_click_button_(
           new FloatingMenuButton(this,
                                  kAutoclickLeftClickIcon,
@@ -138,21 +138,21 @@
     event_type_ = type;
 }
 
-void AutoclickMenuView::UpdatePosition(AutoclickMenuPosition position) {
+void AutoclickMenuView::UpdatePosition(FloatingMenuPosition position) {
   switch (position) {
-    case AutoclickMenuPosition::kBottomRight:
+    case FloatingMenuPosition::kBottomRight:
       position_button_->SetVectorIcon(kAutoclickPositionBottomRightIcon);
       return;
-    case AutoclickMenuPosition::kBottomLeft:
+    case FloatingMenuPosition::kBottomLeft:
       position_button_->SetVectorIcon(kAutoclickPositionBottomLeftIcon);
       return;
-    case AutoclickMenuPosition::kTopLeft:
+    case FloatingMenuPosition::kTopLeft:
       position_button_->SetVectorIcon(kAutoclickPositionTopLeftIcon);
       return;
-    case AutoclickMenuPosition::kTopRight:
+    case FloatingMenuPosition::kTopRight:
       position_button_->SetVectorIcon(kAutoclickPositionTopRightIcon);
       return;
-    case AutoclickMenuPosition::kSystemDefault:
+    case FloatingMenuPosition::kSystemDefault:
       position_button_->SetVectorIcon(base::i18n::IsRTL()
                                           ? kAutoclickPositionBottomLeftIcon
                                           : kAutoclickPositionBottomRightIcon);
@@ -163,25 +163,25 @@
 void AutoclickMenuView::ButtonPressed(views::Button* sender,
                                       const ui::Event& event) {
   if (sender == position_button_) {
-    AutoclickMenuPosition new_position;
+    FloatingMenuPosition new_position;
     // Rotate clockwise throughout the screen positions.
     switch (
         Shell::Get()->accessibility_controller()->GetAutoclickMenuPosition()) {
-      case AutoclickMenuPosition::kBottomRight:
-        new_position = AutoclickMenuPosition::kBottomLeft;
+      case FloatingMenuPosition::kBottomRight:
+        new_position = FloatingMenuPosition::kBottomLeft;
         break;
-      case AutoclickMenuPosition::kBottomLeft:
-        new_position = AutoclickMenuPosition::kTopLeft;
+      case FloatingMenuPosition::kBottomLeft:
+        new_position = FloatingMenuPosition::kTopLeft;
         break;
-      case AutoclickMenuPosition::kTopLeft:
-        new_position = AutoclickMenuPosition::kTopRight;
+      case FloatingMenuPosition::kTopLeft:
+        new_position = FloatingMenuPosition::kTopRight;
         break;
-      case AutoclickMenuPosition::kTopRight:
-        new_position = AutoclickMenuPosition::kBottomRight;
+      case FloatingMenuPosition::kTopRight:
+        new_position = FloatingMenuPosition::kBottomRight;
         break;
-      case AutoclickMenuPosition::kSystemDefault:
-        new_position = base::i18n::IsRTL() ? AutoclickMenuPosition::kTopLeft
-                                           : AutoclickMenuPosition::kBottomLeft;
+      case FloatingMenuPosition::kSystemDefault:
+        new_position = base::i18n::IsRTL() ? FloatingMenuPosition::kTopLeft
+                                           : FloatingMenuPosition::kBottomLeft;
         break;
     }
     Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition(
diff --git a/ash/system/accessibility/autoclick_menu_view.h b/ash/system/accessibility/autoclick_menu_view.h
index 07daf1a8..21843df 100644
--- a/ash/system/accessibility/autoclick_menu_view.h
+++ b/ash/system/accessibility/autoclick_menu_view.h
@@ -28,11 +28,11 @@
     kPause = 7,
   };
 
-  AutoclickMenuView(AutoclickEventType type, AutoclickMenuPosition position);
+  AutoclickMenuView(AutoclickEventType type, FloatingMenuPosition position);
   ~AutoclickMenuView() override = default;
 
   void UpdateEventType(AutoclickEventType type);
-  void UpdatePosition(AutoclickMenuPosition position);
+  void UpdatePosition(FloatingMenuPosition position);
 
   // views::ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
diff --git a/ash/system/screen_layout_observer_unittest.cc b/ash/system/screen_layout_observer_unittest.cc
index 131dbb8..a6175e1 100644
--- a/ash/system/screen_layout_observer_unittest.cc
+++ b/ash/system/screen_layout_observer_unittest.cc
@@ -184,7 +184,8 @@
 
   const int64_t first_display_id =
       display::Screen::GetScreen()->GetPrimaryDisplay().id();
-  const int64_t second_display_id = first_display_id + 1;
+  const int64_t second_display_id =
+      display::GetNextSynthesizedDisplayId(first_display_id);
   display::ManagedDisplayInfo first_display_info =
       display::CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 500, 500));
   display::ManagedDisplayInfo second_display_info =
@@ -310,7 +311,8 @@
 
   const int64_t first_display_id =
       display::Screen::GetScreen()->GetPrimaryDisplay().id();
-  const int64_t second_display_id = first_display_id + 1;
+  const int64_t second_display_id =
+      display::GetNextSynthesizedDisplayId(first_display_id);
   display::ManagedDisplayInfo first_display_info =
       display::CreateDisplayInfo(first_display_id, gfx::Rect(1, 1, 500, 500));
   display::ManagedDisplayInfo second_display_info =
diff --git a/ash/wm/window_cycle_controller_unittest.cc b/ash/wm/window_cycle_controller_unittest.cc
index 104eb41..9e11692 100644
--- a/ash/wm/window_cycle_controller_unittest.cc
+++ b/ash/wm/window_cycle_controller_unittest.cc
@@ -649,7 +649,7 @@
 TEST_F(WindowCycleControllerTest, MultiDisplayPositioning) {
   int64_t primary_id = GetPrimaryDisplay().id();
   display::DisplayIdList list =
-      display::test::CreateDisplayIdListN(2, primary_id, primary_id + 1);
+      display::test::CreateDisplayIdListN(primary_id, 2);
 
   auto placements = {
       display::DisplayPlacement::BOTTOM, display::DisplayPlacement::TOP,
diff --git a/base/util/timer/wall_clock_timer.cc b/base/util/timer/wall_clock_timer.cc
index 6fba83b42..5420ec7 100644
--- a/base/util/timer/wall_clock_timer.cc
+++ b/base/util/timer/wall_clock_timer.cc
@@ -35,10 +35,6 @@
                &WallClockTimer::RunUserTask);
 }
 
-base::Time WallClockTimer::Now() const {
-  return clock_->Now();
-}
-
 void WallClockTimer::Stop() {
   timer_.Stop();
   user_task_.Reset();
@@ -49,14 +45,6 @@
   return timer_.IsRunning();
 }
 
-void WallClockTimer::RunUserTask() {
-  DCHECK(user_task_);
-  std::exchange(user_task_, {}).Run();
-  // TODO(crbug.com/1062410): Remove observer before running |task| so that
-  //|task| is able to call Start().
-  RemoveObserver();
-}
-
 void WallClockTimer::OnResume() {
   // This will actually restart timer with smaller delay
   timer_.Start(posted_from_, desired_run_time_ - Now(), this,
@@ -75,4 +63,14 @@
   }
 }
 
+void WallClockTimer::RunUserTask() {
+  DCHECK(user_task_);
+  RemoveObserver();
+  std::exchange(user_task_, {}).Run();
+}
+
+base::Time WallClockTimer::Now() const {
+  return clock_->Now();
+}
+
 }  // namespace util
diff --git a/base/util/timer/wall_clock_timer.h b/base/util/timer/wall_clock_timer.h
index 962665e..8c955fe9 100644
--- a/base/util/timer/wall_clock_timer.h
+++ b/base/util/timer/wall_clock_timer.h
@@ -49,15 +49,15 @@
 
   ~WallClockTimer() override;
 
-  // Start the timer to run at the given |delay| from now. If the timer is
+  // Starts the timer to run at the given |desired_run_time|. If the timer is
   // already running, it will be replaced to call the given |user_task|.
   virtual void Start(const base::Location& posted_from,
                      base::Time desired_run_time,
                      base::OnceClosure user_task);
 
-  // Start the timer to run at the given |delay| from now. If the timer is
+  // Starts the timer to run at the given |desired_run_time|. If the timer is
   // already running, it will be replaced to call a task formed from
-  // |reviewer->*method|.
+  // |receiver|->*|method|.
   template <class Receiver>
   void Start(const base::Location& posted_from,
              base::Time desired_run_time,
@@ -67,8 +67,7 @@
           base::BindOnce(method, base::Unretained(receiver)));
   }
 
-  // Call this method to stop and cancle the timer. It is a no-op if the timer
-  // is not running.
+  // Stops the timer. It is a no-op if the timer is not running.
   void Stop();
 
   // Returns true if the timer is running.
diff --git a/base/util/timer/wall_clock_timer_unittest.cc b/base/util/timer/wall_clock_timer_unittest.cc
index c93ebdba..cfb5c950 100644
--- a/base/util/timer/wall_clock_timer_unittest.cc
+++ b/base/util/timer/wall_clock_timer_unittest.cc
@@ -43,30 +43,47 @@
 
   ~WallClockTimerTest() override { base::PowerMonitor::ShutdownForTesting(); }
 
+  // Fast-forwards virtual time by |delta|. If |with_power| is true, both
+  // |clock_| and |task_environment_| time will be fast-forwarded. Otherwise,
+  // only |clock_| time will be changed to mimic the behavior when machine is
+  // suspended.
+  // Power event will be triggered if |with_power| is set to false.
+  void FastForwardBy(base::TimeDelta delay, bool with_power = true) {
+    if (!with_power)
+      mock_power_monitor_source_->Suspend();
+
+    clock_.SetNow(clock_.Now() + delay);
+
+    if (with_power) {
+      task_environment_.FastForwardBy(delay);
+    } else {
+      mock_power_monitor_source_->Resume();
+      task_environment_.RunUntilIdle();
+    }
+  }
+
   // Owned by power_monitor_. Use this to simulate a power suspend and resume.
   StubPowerMonitorSource* mock_power_monitor_source_ = nullptr;
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::SimpleTestClock clock_;
 };
 
 TEST_F(WallClockTimerTest, PowerResume) {
   ::testing::StrictMock<base::MockOnceClosure> callback;
-  base::SimpleTestClock clock;
   // Set up a WallClockTimer that will fire in one minute.
-  WallClockTimer wall_clock_timer(&clock, task_environment_.GetMockTickClock());
+  WallClockTimer wall_clock_timer(&clock_,
+                                  task_environment_.GetMockTickClock());
   constexpr auto delay = base::TimeDelta::FromMinutes(1);
   const auto start_time = base::Time::Now();
   const auto run_time = start_time + delay;
-  clock.SetNow(start_time);
+  clock_.SetNow(start_time);
   wall_clock_timer.Start(FROM_HERE, run_time, callback.Get());
   EXPECT_EQ(wall_clock_timer.desired_run_time(), start_time + delay);
 
-  mock_power_monitor_source_->Suspend();
   // Pretend that time jumps forward 30 seconds while the machine is suspended.
   constexpr auto past_time = base::TimeDelta::FromSeconds(30);
-  clock.SetNow(start_time + past_time);
-  mock_power_monitor_source_->Resume();
-  task_environment_.RunUntilIdle();
+  FastForwardBy(past_time, /*with_power=*/false);
   // Ensure that the timer has not yet fired.
   ::testing::Mock::VerifyAndClearExpectations(&callback);
   EXPECT_EQ(wall_clock_timer.desired_run_time(), start_time + delay);
@@ -75,10 +92,70 @@
   EXPECT_CALL(callback, Run());
   // Both Time::Now() and |task_environment_| MockTickClock::Now()
   // go forward by (|delay| - |past_time|):
-  clock.SetNow(start_time + delay);
-  task_environment_.FastForwardBy(delay - past_time);
+  FastForwardBy(delay - past_time);
   ::testing::Mock::VerifyAndClearExpectations(&callback);
   EXPECT_FALSE(wall_clock_timer.IsRunning());
 }
 
+TEST_F(WallClockTimerTest, UseTimerTwiceInRow) {
+  ::testing::StrictMock<base::MockOnceClosure> first_callback;
+  ::testing::StrictMock<base::MockOnceClosure> second_callback;
+  const auto start_time = base::Time::Now();
+  clock_.SetNow(start_time);
+
+  // Set up a WallClockTimer that will invoke |first_callback| in one minute.
+  // Once it's done, it will invoke |second_callback| after the other minute.
+  WallClockTimer wall_clock_timer(&clock_,
+                                  task_environment_.GetMockTickClock());
+  constexpr auto delay = base::TimeDelta::FromMinutes(1);
+  wall_clock_timer.Start(FROM_HERE, clock_.Now() + delay, first_callback.Get());
+  EXPECT_CALL(first_callback, Run())
+      .WillOnce(::testing::InvokeWithoutArgs(
+          [this, &wall_clock_timer, &second_callback, delay]() {
+            wall_clock_timer.Start(FROM_HERE, clock_.Now() + delay,
+                                   second_callback.Get());
+          }));
+
+  FastForwardBy(delay);
+  ::testing::Mock::VerifyAndClearExpectations(&first_callback);
+  ::testing::Mock::VerifyAndClearExpectations(&second_callback);
+
+  // When the |wall_clock_time| is used for the second time, it can still handle
+  // power suspension properly.
+  constexpr auto past_time = base::TimeDelta::FromSeconds(30);
+  FastForwardBy(past_time, /*with_power=*/false);
+  ::testing::Mock::VerifyAndClearExpectations(&second_callback);
+
+  EXPECT_CALL(second_callback, Run());
+  FastForwardBy(delay - past_time);
+  ::testing::Mock::VerifyAndClearExpectations(&second_callback);
+}
+
+TEST_F(WallClockTimerTest, Stop) {
+  ::testing::StrictMock<base::MockOnceClosure> callback;
+  const auto start_time = base::Time::Now();
+  clock_.SetNow(start_time);
+
+  // Set up a WallClockTimer.
+  WallClockTimer wall_clock_timer(&clock_,
+                                  task_environment_.GetMockTickClock());
+  constexpr auto delay = base::TimeDelta::FromMinutes(1);
+  wall_clock_timer.Start(FROM_HERE, clock_.Now() + delay, callback.Get());
+
+  // After 20 seconds, timer is stopped.
+  constexpr auto past_time = base::TimeDelta::FromSeconds(20);
+  FastForwardBy(past_time);
+  EXPECT_TRUE(wall_clock_timer.IsRunning());
+  wall_clock_timer.Stop();
+  EXPECT_FALSE(wall_clock_timer.IsRunning());
+
+  // When power is suspends and resumed, timer won't be resumed.
+  FastForwardBy(past_time, /*with_power=*/false);
+  EXPECT_FALSE(wall_clock_timer.IsRunning());
+
+  // Timer won't fire when desired run time is reached.
+  FastForwardBy(delay - past_time * 2);
+  ::testing::Mock::VerifyAndClearExpectations(&callback);
+}
+
 }  // namespace util
diff --git a/build/android/docs/java_toolchain.md b/build/android/docs/java_toolchain.md
index b8edb46..fb764684 100644
--- a/build/android/docs/java_toolchain.md
+++ b/build/android/docs/java_toolchain.md
@@ -145,12 +145,12 @@
 This step is skipped when building using [Incremental Install].
 
 When `is_java_debug = true`:
-* [d8] merges all library `.dex.jar` files into a final `.dex.zip`.
+* [d8] merges all library `.dex.jar` files into a final `.mergeddex.jar`.
 
 When `is_java_debug = false`:
 * [R8] performs whole-program optimization on all library `lib.java` `.jar`
-  files and outputs a final `.dex.zip`.
-  * For App Bundles, R8 creates a single `.dex.zip` with the code from all
+  files and outputs a final `.r8dex.jar`.
+  * For App Bundles, R8 creates a single `.r8dex.jar` with the code from all
     modules.
 
 [Incremental Install]: /build/android/incremental_install/README.md
@@ -160,7 +160,7 @@
 
 This step happens only when `is_java_debug = false`.
 
-* [dexsplitter.py] splits the single `.dex.zip` into per-module `.dex.zip`
+* [dexsplitter.py] splits the single `*dex.jar` into per-module `*dex.jar`
   files.
 
 ## Test APKs with apk_under_test
diff --git a/build/android/gyp/dex.py b/build/android/gyp/dex.py
index 8dfdeb3..d1a9535 100755
--- a/build/android/gyp/dex.py
+++ b/build/android/gyp/dex.py
@@ -258,7 +258,7 @@
   """
   ordered_files = []  # List of (archive name, file name)
   for f in dex_files:
-    if f.endswith('classes.dex.zip'):
+    if f.endswith('dex.jar'):
       ordered_files.append(('classes.dex', f))
       break
   if not ordered_files:
diff --git a/build/android/gyp/dexsplitter.py b/build/android/gyp/dexsplitter.py
index 7bbc066..8e8230b 100755
--- a/build/android/gyp/dexsplitter.py
+++ b/build/android/gyp/dexsplitter.py
@@ -101,15 +101,15 @@
         if os.path.exists(module_dex_file):
           curr_location_to_dest.append((module_dex_file, dest))
         else:
-          module_dex_file += '.zip'
+          module_dex_file += '.jar'
           assert os.path.exists(
               module_dex_file), 'Dexsplitter tool output not found.'
-          curr_location_to_dest.append((module_dex_file + '.zip', dest))
+          curr_location_to_dest.append((module_dex_file + '.jar', dest))
 
     for curr_location, dest in curr_location_to_dest:
       with build_utils.AtomicOutput(dest) as f:
-        if curr_location.endswith('.zip'):
-          if dest.endswith('.zip'):
+        if curr_location.endswith('.jar'):
+          if dest.endswith('.jar'):
             shutil.copy(curr_location, f.name)
           else:
             with zipfile.ZipFile(curr_location, 'r') as z:
@@ -119,7 +119,7 @@
                   options.input_dex_zip)
               z.extract(namelist[0], f.name)
         else:
-          if dest.endswith('.zip'):
+          if dest.endswith('.jar'):
             build_utils.ZipDir(
                 f.name, os.path.abspath(os.path.join(curr_location, os.pardir)))
           else:
diff --git a/build/android/gyp/extract_unwind_tables.py b/build/android/gyp/extract_unwind_tables.py
index ea13c6a..b20f740 100755
--- a/build/android/gyp/extract_unwind_tables.py
+++ b/build/android/gyp/extract_unwind_tables.py
@@ -254,10 +254,8 @@
     _Write2Bytes(out_file, data)
 
 
-def _ParseCfiData(sym_file, output_path):
-  with open(sym_file, 'r') as f:
-    cfi_data =  _GetAllCfiRows(f)
-
+def _ParseCfiData(sym_stream, output_path):
+  cfi_data = _GetAllCfiRows(sym_stream)
   with open(output_path, 'wb') as out_file:
     _WriteCfiData(cfi_data, out_file)
 
@@ -275,13 +273,11 @@
       help='The path of the dump_syms binary')
 
   args = parser.parse_args()
+  cmd = ['./' + args.dump_syms_path, args.input_path]
+  proc = subprocess.Popen(cmd, bufsize=-1, stdout=subprocess.PIPE)
+  _ParseCfiData(proc.stdout, args.output_path)
+  assert proc.wait() == 0
 
-  with tempfile.NamedTemporaryFile() as sym_file:
-    out = subprocess.call(
-        ['./' +args.dump_syms_path, args.input_path], stdout=sym_file)
-    assert not out
-    sym_file.flush()
-    _ParseCfiData(sym_file.name, args.output_path)
   return 0
 
 if __name__ == '__main__':
diff --git a/build/android/gyp/extract_unwind_tables_tests.py b/build/android/gyp/extract_unwind_tables_tests.py
index e686607..7f9d0de7 100755
--- a/build/android/gyp/extract_unwind_tables_tests.py
+++ b/build/android/gyp/extract_unwind_tables_tests.py
@@ -24,9 +24,8 @@
 
 class TestExtractUnwindTables(unittest.TestCase):
   def testExtractCfi(self):
-    with tempfile.NamedTemporaryFile() as input_file, \
-        tempfile.NamedTemporaryFile() as output_file:
-      input_file.write("""
+    with tempfile.NamedTemporaryFile() as output_file:
+      test_data_lines = """
 MODULE Linux arm CDE12FE1DF2B37A9C6560B4CBEE056420 lib_chrome.so
 INFO CODE_ID E12FE1CD2BDFA937C6560B4CBEE05642
 FILE 0 ../../base/allocator/allocator_check.cc
@@ -63,9 +62,8 @@
 STACK CFI 3b92118 .cfa: r7 16 + .ra: .cfa -20 + ^
 STACK CFI INIT 3b93214 fffff .cfa: sp 0 + .ra: lr
 STACK CFI 3b93218 .cfa: r7 16 + .ra: .cfa -4 + ^
-""")
-      input_file.flush()
-      extract_unwind_tables._ParseCfiData(input_file.name, output_file.name)
+""".splitlines()
+      extract_unwind_tables._ParseCfiData(test_data_lines, output_file.name)
 
       expected_cfi_data = {
         0xe1a1e4 : [0x2, 0x11, 0x4, 0x50],
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 7d8306a..0f90b2a 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -11,7 +11,9 @@
 import os
 import posixpath
 import re
+import shutil
 import sys
+import tempfile
 import time
 
 from devil import base_error
@@ -93,6 +95,8 @@
       r'(?P<description>[-\w]+)\.'
       r'(?P<device_model_sdk>[-\w]+)\.png')
 
+_DEVICE_GOLD_DIR = 'skia_gold'
+
 
 @contextlib.contextmanager
 def _LogTestEndpoints(device, test_name):
@@ -136,6 +140,7 @@
     self._context_managers = collections.defaultdict(list)
     self._flag_changers = {}
     self._shared_prefs_to_restore = []
+    self._skia_gold_work_dir = None
 
   #override
   def TestPackage(self):
@@ -361,6 +366,10 @@
     self._env.parallel_devices.pMap(
         individual_device_set_up,
         self._test_instance.GetDataDependencies())
+    # Created here instead of on a per-test basis so that the downloaded
+    # expectations can be re-used between tests, saving a significant amount
+    # of time.
+    self._skia_gold_work_dir = tempfile.mkdtemp()
     if self._test_instance.wait_for_java_debugger:
       apk = self._test_instance.apk_under_test or self._test_instance.test_apk
       logging.warning('*' * 80)
@@ -370,6 +379,8 @@
 
   #override
   def TearDown(self):
+    shutil.rmtree(self._skia_gold_work_dir)
+    self._skia_gold_work_dir = None
     # By default, teardown will invoke ADB. When receiving SIGTERM due to a
     # timeout, there's a high probability that ADB is non-responsive. In these
     # cases, sending an ADB command will potentially take a long time to time
@@ -906,39 +917,41 @@
 
   def _ProcessSkiaGoldRenderTestResults(
       self, device, render_tests_device_output_dir, results):
-    gold_dir = posixpath.join(render_tests_device_output_dir, 'skia_gold')
+    gold_dir = posixpath.join(render_tests_device_output_dir, _DEVICE_GOLD_DIR)
     if not device.FileExists(gold_dir):
       return
 
     gold_properties = self._test_instance.skia_gold_properties
-    with tempfile_ext.NamedTemporaryDirectory() as working_dir:
+    with tempfile_ext.NamedTemporaryDirectory() as host_dir:
       gold_session = gold_utils.SkiaGoldSession(
-          working_dir=working_dir, gold_properties=gold_properties)
+          working_dir=self._skia_gold_work_dir, gold_properties=gold_properties)
       use_luci = not (gold_properties.local_pixel_tests
                       or gold_properties.no_luci_auth)
-      for image_name in device.ListDirectory(gold_dir):
+
+      # Pull everything at once instead of pulling individually, as it's
+      # slightly faster since each command over adb has some overhead compared
+      # to doing the same thing locally.
+      device.PullFile(gold_dir, host_dir)
+      host_dir = os.path.join(host_dir, _DEVICE_GOLD_DIR)
+      for image_name in os.listdir(host_dir):
         if not image_name.endswith('.png'):
           continue
 
         render_name = image_name[:-4]
         json_name = render_name + '.json'
-        device_json_path = posixpath.join(gold_dir, json_name)
-        if not device.FileExists(device_json_path):
+        json_path = os.path.join(host_dir, json_name)
+        image_path = os.path.join(host_dir, image_name)
+        if not os.path.exists(json_path):
           _FailTestIfNecessary(results)
           _AppendToLog(
               results, 'Unable to find corresponding JSON file for image %s '
               'when doing Skia Gold comparison.' % image_name)
           continue
 
-        host_image_path = os.path.join(working_dir, image_name)
-        device.PullFile(posixpath.join(gold_dir, image_name), host_image_path)
-        host_json_path = os.path.join(working_dir, json_name)
-        device.PullFile(device_json_path, host_json_path)
-
         status, error = gold_session.RunComparison(
             name=render_name,
-            keys_file=host_json_path,
-            png_file=host_image_path,
+            keys_file=json_path,
+            png_file=image_path,
             output_manager=self._env.output_manager,
             use_luci=use_luci)
 
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 1640a1d..aba91cd 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2349,7 +2349,7 @@
         defined(invoker.static_library_dependent_targets) && _proguard_enabled
     if (_is_static_library_provider) {
       _static_library_sync_dex_path =
-          "$_out_dir/static_library_synchronized_proguard.classes.dex.zip"
+          "$_out_dir/static_library_synchronized_proguard.r8dex.jar"
       _resource_ids_provider_deps = []
       foreach(_target, invoker.static_library_dependent_targets) {
         if (_target.is_resource_ids_provider) {
@@ -2397,7 +2397,11 @@
     if (!_incremental_apk) {
       # Bundle modules don't build the dex here, but need to write this path
       # to their .build_config file.
-      _final_dex_path = "$_out_dir/classes.dex.zip"
+      if (_proguard_enabled) {
+        _final_dex_path = "$_base_path.r8dex.jar"
+      } else {
+        _final_dex_path = "$_base_path.mergeddex.jar"
+      }
     }
 
     _android_manifest =
diff --git a/build/fuchsia/OWNERS b/build/fuchsia/OWNERS
index 407e79e..8e2a773 100644
--- a/build/fuchsia/OWNERS
+++ b/build/fuchsia/OWNERS
@@ -1,5 +1,6 @@
-kmarshall@chromium.org
+ddorwin@chromium.org
 fdegans@chromium.org
+kmarshall@chromium.org
 sergeyu@chromium.org
 wez@chromium.org
 
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 9fcbfa3..c1763e8 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200407.1.1
\ No newline at end of file
+0.20200409.1.1
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 9fcbfa3..c1763e8 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200407.1.1
\ No newline at end of file
+0.20200409.1.1
\ No newline at end of file
diff --git a/cc/animation/animation_host.cc b/cc/animation/animation_host.cc
index 75cc47a..542c680 100644
--- a/cc/animation/animation_host.cc
+++ b/cc/animation/animation_host.cc
@@ -305,6 +305,12 @@
   host_impl->main_thread_animations_count_ = main_thread_animations_count_;
   host_impl->current_frame_had_raf_ = current_frame_had_raf_;
   host_impl->next_frame_has_pending_raf_ = next_frame_has_pending_raf_;
+
+  // The pending info list is cleared in LayerTreeHostImpl::CommitComplete
+  // and should be empty when pushing properties.
+  DCHECK(host_impl->pending_throughput_tracker_infos_.empty());
+  host_impl->pending_throughput_tracker_infos_ =
+      TakePendingThroughputTrackerInfos();
 }
 
 scoped_refptr<ElementAnimations>
@@ -802,4 +808,24 @@
   return next_frame_has_pending_raf_;
 }
 
+AnimationHost::PendingThroughputTrackerInfos
+AnimationHost::TakePendingThroughputTrackerInfos() {
+  PendingThroughputTrackerInfos infos =
+      std::move(pending_throughput_tracker_infos_);
+  pending_throughput_tracker_infos_ = {};
+  return infos;
+}
+
+void AnimationHost::StartThroughputTracking(
+    TrackedAnimationSequenceId sequence_id) {
+  pending_throughput_tracker_infos_.push_back({sequence_id, true});
+  SetNeedsPushProperties();
+}
+
+void AnimationHost::StopThroughputTracking(
+    TrackedAnimationSequenceId sequnece_id) {
+  pending_throughput_tracker_infos_.push_back({sequnece_id, false});
+  SetNeedsPushProperties();
+}
+
 }  // namespace cc
diff --git a/cc/animation/animation_host.h b/cc/animation/animation_host.h
index 28d9f7b..d5085ee 100644
--- a/cc/animation/animation_host.h
+++ b/cc/animation/animation_host.h
@@ -212,6 +212,12 @@
   bool HasCustomPropertyAnimations() const override;
   bool CurrentFrameHadRAF() const override;
   bool NextFrameHasPendingRAF() const override;
+  PendingThroughputTrackerInfos TakePendingThroughputTrackerInfos() override;
+
+  // Starts/stops throughput tracking represented by |sequence_id|.
+  void StartThroughputTracking(TrackedAnimationSequenceId sequence_id);
+  void StopThroughputTracking(TrackedAnimationSequenceId sequnece_id);
+
   void SetAnimationCounts(size_t total_animations_count,
                           bool current_frame_had_raf,
                           bool next_frame_has_pending_raf);
@@ -268,6 +274,8 @@
   bool current_frame_had_raf_ = false;
   bool next_frame_has_pending_raf_ = false;
 
+  PendingThroughputTrackerInfos pending_throughput_tracker_infos_;
+
   base::WeakPtrFactory<AnimationHost> weak_factory_{this};
 };
 
diff --git a/cc/metrics/compositor_frame_reporting_controller.cc b/cc/metrics/compositor_frame_reporting_controller.cc
index ab994af..8a0f6dcb 100644
--- a/cc/metrics/compositor_frame_reporting_controller.cc
+++ b/cc/metrics/compositor_frame_reporting_controller.cc
@@ -153,64 +153,96 @@
   // Impl work is finished.
   bool is_activated_frame_new =
       (last_activated_frame_id != last_submitted_frame_id_);
+
+  // Temporarily hold the main and impl reporter until they are moved into
+  // |submitted_compositor_frames_|
+  std::unique_ptr<CompositorFrameReporter> main_reporter;
+  std::unique_ptr<CompositorFrameReporter> impl_reporter;
+
+  // If |is_activated_frame_new| is true, |main_reporter| is guaranteed to
+  // be set, and |impl_reporter| may or may not be set; otherwise,
+  // |impl_reporter| is guaranteed to be set, and |main_reporter| will not be
+  // set.
   if (is_activated_frame_new) {
     DCHECK_EQ(reporters_[PipelineStage::kActivate]->frame_id_,
               last_activated_frame_id);
     // The reporter in activate state can be submitted
+    main_reporter = std::move(reporters_[PipelineStage::kActivate]);
   } else {
-    // There is no Main damage, which is possible if (1) there was no beginMain
-    // so the reporter in beginImpl will be submitted or (2) the beginMain is
-    // sent and aborted, so the reporter in beginMain will be submitted or (3)
-    // the main thread work is not done yet and the impl portion should be
-    // reported.
-    if (CanSubmitImplFrame(current_frame_id)) {
-      auto& reporter = reporters_[PipelineStage::kBeginImplFrame];
+    DCHECK(!reporters_[PipelineStage::kActivate]);
+  }
+
+  // There is no Main damage, which is possible if (1) there was no beginMain
+  // so the reporter in beginImpl will be submitted or (2) the beginMain is
+  // sent and aborted, so the reporter in beginMain will be submitted or (3)
+  // the main thread work is not done yet and the impl portion should be
+  // reported.
+  if (CanSubmitImplFrame(current_frame_id)) {
+    auto& reporter = reporters_[PipelineStage::kBeginImplFrame];
+    reporter->StartStage(StageType::kEndActivateToSubmitCompositorFrame,
+                         reporter->impl_frame_finish_time());
+    AdvanceReporterStage(PipelineStage::kBeginImplFrame,
+                         PipelineStage::kActivate);
+    impl_reporter = std::move(reporters_[PipelineStage::kActivate]);
+  } else if (CanSubmitMainFrame(current_frame_id)) {
+    auto& reporter = reporters_[PipelineStage::kBeginMainFrame];
+    reporter->StartStage(StageType::kEndActivateToSubmitCompositorFrame,
+                         reporter->impl_frame_finish_time());
+    AdvanceReporterStage(PipelineStage::kBeginMainFrame,
+                         PipelineStage::kActivate);
+    impl_reporter = std::move(reporters_[PipelineStage::kActivate]);
+  } else {
+    // No main damage: the submitted frame might have unfinished main thread
+    // work, which in that case the BeginImpl portion can be reported.
+    auto reporter = RestoreReporterAtBeginImpl(current_frame_id);
+    // The method will return nullptr if Impl reporter has been submitted
+    // prior to BeginMainFrame.
+    if (reporter) {
       reporter->StartStage(StageType::kEndActivateToSubmitCompositorFrame,
                            reporter->impl_frame_finish_time());
-      AdvanceReporterStage(PipelineStage::kBeginImplFrame,
-                           PipelineStage::kActivate);
-    } else if (CanSubmitMainFrame(current_frame_id)) {
-      auto& reporter = reporters_[PipelineStage::kBeginMainFrame];
-      reporter->StartStage(StageType::kEndActivateToSubmitCompositorFrame,
-                           reporter->impl_frame_finish_time());
-      AdvanceReporterStage(PipelineStage::kBeginMainFrame,
-                           PipelineStage::kActivate);
-    } else {
-      // The submitted frame might have unfinished main thread work, which in
-      // that case the BeginImpl portion can be reported.
-      auto reporter = RestoreReporterAtBeginImpl(current_frame_id);
-      // The method will return nullptr if Impl reporter has been submitted
-      // prior to BeginMainFrame.
-      if (!reporter)
-        return;
-      reporter->StartStage(StageType::kEndActivateToSubmitCompositorFrame,
-                           reporter->impl_frame_finish_time());
-      reporters_[PipelineStage::kActivate] = std::move(reporter);
+      impl_reporter = std::move(reporter);
     }
   }
 
+  if (!events_metrics.main_event_metrics.empty()) {
+    DCHECK(main_reporter);
+  }
+
+  // When |impl_reporter| does not exist, but there are still impl-side metrics,
+  // merge the main and impl metrics and pass the combined vector into
+  // |main_reporter|.
+  if (!impl_reporter && !events_metrics.impl_event_metrics.empty()) {
+    DCHECK(main_reporter);
+    // If there are impl events, there must be a reporter with
+    // |current_frame_id|.
+    DCHECK_EQ(main_reporter->frame_id_, current_frame_id);
+    events_metrics.main_event_metrics.reserve(
+        events_metrics.main_event_metrics.size() +
+        events_metrics.impl_event_metrics.size());
+    events_metrics.main_event_metrics.insert(
+        events_metrics.main_event_metrics.end(),
+        events_metrics.impl_event_metrics.begin(),
+        events_metrics.impl_event_metrics.end());
+  }
+
   last_submitted_frame_id_ = last_activated_frame_id;
-  std::unique_ptr<CompositorFrameReporter> submitted_reporter =
-      std::move(reporters_[PipelineStage::kActivate]);
-  submitted_reporter->StartStage(
-      StageType::kSubmitCompositorFrameToPresentationCompositorFrame, Now());
+  if (main_reporter) {
+    main_reporter->StartStage(
+        StageType::kSubmitCompositorFrameToPresentationCompositorFrame, Now());
+    main_reporter->SetEventsMetrics(
+        std::move(events_metrics.main_event_metrics));
+    submitted_compositor_frames_.emplace_back(frame_token,
+                                              std::move(main_reporter));
+  }
 
-  // TODO(mjzhang): The main and impl vectors are combined to preserve the
-  // current behavior. In a subsequent CL, an impl reporter will also be added
-  // for submission and the two vectors will be kept separate and moved into
-  // their corresponding reporter.
-  std::vector<EventMetrics> combined_event_metrics =
-      std::move(events_metrics.main_event_metrics);
-
-  combined_event_metrics.reserve(combined_event_metrics.size() +
-                                 events_metrics.impl_event_metrics.size());
-  combined_event_metrics.insert(combined_event_metrics.end(),
-                                events_metrics.impl_event_metrics.begin(),
-                                events_metrics.impl_event_metrics.end());
-
-  submitted_reporter->SetEventsMetrics(std::move(combined_event_metrics));
-  submitted_compositor_frames_.emplace_back(frame_token,
-                                            std::move(submitted_reporter));
+  if (impl_reporter) {
+    impl_reporter->StartStage(
+        StageType::kSubmitCompositorFrameToPresentationCompositorFrame, Now());
+    impl_reporter->SetEventsMetrics(
+        std::move(events_metrics.impl_event_metrics));
+    submitted_compositor_frames_.emplace_back(frame_token,
+                                              std::move(impl_reporter));
+  }
 }
 
 void CompositorFrameReportingController::DidNotProduceFrame(
diff --git a/cc/metrics/compositor_frame_reporting_controller_unittest.cc b/cc/metrics/compositor_frame_reporting_controller_unittest.cc
index d59cb64..64da64b 100644
--- a/cc/metrics/compositor_frame_reporting_controller_unittest.cc
+++ b/cc/metrics/compositor_frame_reporting_controller_unittest.cc
@@ -355,7 +355,7 @@
   histogram_tester.ExpectTotalCount(
       "CompositorLatency.DroppedFrame.SendBeginMainFrameToCommit", 0);
   histogram_tester.ExpectTotalCount(
-      "CompositorLatency.BeginImplFrameToSendBeginMainFrame", 1);
+      "CompositorLatency.BeginImplFrameToSendBeginMainFrame", 2);
   histogram_tester.ExpectTotalCount(
       "CompositorLatency.SendBeginMainFrameToCommit", 1);
   histogram_tester.ExpectTotalCount("CompositorLatency.Commit", 1);
@@ -363,10 +363,10 @@
                                     1);
   histogram_tester.ExpectTotalCount("CompositorLatency.Activation", 1);
   histogram_tester.ExpectTotalCount(
-      "CompositorLatency.EndActivateToSubmitCompositorFrame", 1);
+      "CompositorLatency.EndActivateToSubmitCompositorFrame", 2);
   histogram_tester.ExpectTotalCount(
       "CompositorLatency.SubmitCompositorFrameToPresentationCompositorFrame",
-      1);
+      2);
 }
 
 TEST_F(CompositorFrameReportingControllerTest, MainFrameAborted) {
@@ -422,18 +422,18 @@
   histogram_tester.ExpectTotalCount(
       "CompositorLatency.DroppedFrame.BeginImplFrameToSendBeginMainFrame", 0);
   histogram_tester.ExpectTotalCount(
-      "CompositorLatency.BeginImplFrameToSendBeginMainFrame", 1);
+      "CompositorLatency.BeginImplFrameToSendBeginMainFrame", 2);
   histogram_tester.ExpectTotalCount(
-      "CompositorLatency.SendBeginMainFrameToCommit", 1);
+      "CompositorLatency.SendBeginMainFrameToCommit", 2);
   histogram_tester.ExpectTotalCount("CompositorLatency.Commit", 1);
   histogram_tester.ExpectTotalCount("CompositorLatency.EndCommitToActivation",
                                     1);
   histogram_tester.ExpectTotalCount("CompositorLatency.Activation", 1);
   histogram_tester.ExpectTotalCount(
-      "CompositorLatency.EndActivateToSubmitCompositorFrame", 1);
+      "CompositorLatency.EndActivateToSubmitCompositorFrame", 2);
   histogram_tester.ExpectTotalCount(
       "CompositorLatency.SubmitCompositorFrameToPresentationCompositorFrame",
-      1);
+      2);
   reporting_controller_.DidSubmitCompositorFrame(2, current_id_2, current_id_1,
                                                  {});
   reporting_controller_.DidPresentCompositorFrame(2, details);
@@ -482,6 +482,9 @@
   viz::BeginFrameId current_id_2(1, 2);
   viz::BeginFrameArgs args_2 = SimulateBeginFrameArgs(current_id_2);
 
+  viz::BeginFrameId current_id_3(1, 3);
+  viz::BeginFrameArgs args_3 = SimulateBeginFrameArgs(current_id_3);
+
   viz::FrameTimingDetails details = {};
   reporting_controller_.WillBeginImplFrame(args_1);
   reporting_controller_.OnFinishImplFrame(current_id_1);
@@ -530,6 +533,30 @@
   histogram_tester.ExpectTotalCount(
       "CompositorLatency.SubmitCompositorFrameToPresentationCompositorFrame",
       2);
+
+  reporting_controller_.WillBeginImplFrame(args_3);
+  reporting_controller_.OnFinishImplFrame(current_id_3);
+  reporting_controller_.WillCommit();
+  reporting_controller_.DidCommit();
+  reporting_controller_.WillActivate();
+  reporting_controller_.DidActivate();
+  reporting_controller_.DidSubmitCompositorFrame(3, current_id_3, current_id_2,
+                                                 {});
+  reporting_controller_.DidPresentCompositorFrame(3, details);
+
+  histogram_tester.ExpectTotalCount(
+      "CompositorLatency.BeginImplFrameToSendBeginMainFrame", 4);
+  histogram_tester.ExpectTotalCount(
+      "CompositorLatency.SendBeginMainFrameToCommit", 2);
+  histogram_tester.ExpectTotalCount("CompositorLatency.Commit", 2);
+  histogram_tester.ExpectTotalCount("CompositorLatency.EndCommitToActivation",
+                                    2);
+  histogram_tester.ExpectTotalCount("CompositorLatency.Activation", 2);
+  histogram_tester.ExpectTotalCount(
+      "CompositorLatency.EndActivateToSubmitCompositorFrame", 4);
+  histogram_tester.ExpectTotalCount(
+      "CompositorLatency.SubmitCompositorFrameToPresentationCompositorFrame",
+      4);
 }
 
 TEST_F(CompositorFrameReportingControllerTest, LongMainFrame2) {
diff --git a/cc/test/mock_mutator_host.h b/cc/test/mock_mutator_host.h
index de71750e..8966718 100644
--- a/cc/test/mock_mutator_host.h
+++ b/cc/test/mock_mutator_host.h
@@ -102,6 +102,8 @@
   MOCK_CONST_METHOD0(HasCustomPropertyAnimations, bool());
   MOCK_CONST_METHOD0(CurrentFrameHadRAF, bool());
   MOCK_CONST_METHOD0(NextFrameHasPendingRAF, bool());
+  MOCK_METHOD0(TakePendingThroughputTrackerInfos,
+               PendingThroughputTrackerInfos());
 };
 
 }  // namespace cc
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 42cc0cf18..f97fa37 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -515,6 +515,10 @@
     frame_trackers_.StartSequence(
         FrameSequenceTrackerType::kMainThreadAnimation);
   }
+
+  // TODO(crbug.com/1021774): Start/stop custom FrameSequenceTracker based on
+  // pending info.
+  auto ignored = mutator_host_->TakePendingThroughputTrackerInfos();
 }
 
 void LayerTreeHostImpl::UpdateSyncTreeAfterCommitOrImplSideInvalidation() {
diff --git a/cc/trees/mutator_host.h b/cc/trees/mutator_host.h
index 27b4562..103d75f 100644
--- a/cc/trees/mutator_host.h
+++ b/cc/trees/mutator_host.h
@@ -6,6 +6,7 @@
 #define CC_TREES_MUTATOR_HOST_H_
 
 #include <memory>
+#include <vector>
 
 #include "base/callback_forward.h"
 #include "base/time/time.h"
@@ -39,7 +40,7 @@
 // MutatorHostClient interface.
 class MutatorHost {
  public:
-  virtual ~MutatorHost() {}
+  virtual ~MutatorHost() = default;
 
   virtual std::unique_ptr<MutatorHost> CreateImplInstance(
       bool supports_impl_scrolling) const = 0;
@@ -163,11 +164,24 @@
   virtual bool HasCustomPropertyAnimations() const = 0;
   virtual bool CurrentFrameHadRAF() const = 0;
   virtual bool NextFrameHasPendingRAF() const = 0;
+
+  using TrackedAnimationSequenceId = size_t;
+  struct PendingThroughputTrackerInfo {
+    // Id of a tracked animation sequence.
+    TrackedAnimationSequenceId id = 0u;
+    // True means the tracking for |id| is pending to start and false means
+    // the tracking is pending to stop.
+    bool start = false;
+  };
+  // Takes info of throughput trackers that are pending start or stop.
+  using PendingThroughputTrackerInfos =
+      std::vector<PendingThroughputTrackerInfo>;
+  virtual PendingThroughputTrackerInfos TakePendingThroughputTrackerInfos() = 0;
 };
 
 class MutatorEvents {
  public:
-  virtual ~MutatorEvents() {}
+  virtual ~MutatorEvents() = default;
   virtual bool IsEmpty() const = 0;
 };
 
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 43ce7bc..88f98a5 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1571,6 +1571,7 @@
   "java/src/org/chromium/chrome/browser/sync/ui/PassphraseTypeDialogFragment.java",
   "java/src/org/chromium/chrome/browser/tab/AccessibilityVisibilityHandler.java",
   "java/src/org/chromium/chrome/browser/tab/AuthenticatorNavigationInterceptor.java",
+  "java/src/org/chromium/chrome/browser/tab/AuthenticatorNavigationInterceptorTabHelper.java",
   "java/src/org/chromium/chrome/browser/tab/EmptyTabObserver.java",
   "java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java",
   "java/src/org/chromium/chrome/browser/tab/RedirectHandlerTabHelper.java",
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
index 1bfdc221..c096f42 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
@@ -11,6 +11,7 @@
 import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
 import static android.support.test.espresso.matcher.ViewMatchers.Visibility.GONE;
 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
 import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
 import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
@@ -34,6 +35,7 @@
 import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.rotateDeviceToOrientation;
 import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.verifyTabModelTabCount;
 import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.verifyTabSwitcherCardCount;
+import static org.chromium.chrome.test.util.ViewUtils.waitForView;
 import static org.chromium.chrome.test.util.browser.RecyclerViewTestUtils.waitForStableRecyclerView;
 import static org.chromium.components.embedder_support.util.UrlConstants.NTP_URL;
 import static org.chromium.content_public.browser.test.util.CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL;
@@ -1571,6 +1573,38 @@
         assertNotEquals(googleDrawable, iconImageView.getDrawable());
     }
 
+    @Test
+    @MediumTest
+    @EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
+    @DisableFeatures(ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study")
+    public void testTabGroupManualSelection() throws InterruptedException {
+        ChromeTabbedActivity cta = mActivityTestRule.getActivity();
+        TabSelectionEditorTestingRobot robot = new TabSelectionEditorTestingRobot();
+        createTabs(cta, false, 3);
+        enterTabSwitcher(cta);
+        onView(allOf(withParent(withId(R.id.compositor_view_holder)), withId(R.id.tab_list_view)))
+                .check(TabCountAssertion.havingTabCount(3));
+
+        enterTabGroupManualSelection(cta);
+        robot.resultRobot.verifyTabSelectionEditorIsVisible();
+
+        // Group first two tabs.
+        robot.actionRobot.clickItemAtAdapterPosition(0);
+        robot.actionRobot.clickItemAtAdapterPosition(1);
+        robot.actionRobot.clickToolbarActionButton();
+
+        // Exit manual selection mode, back to tab switcher.
+        robot.resultRobot.verifyTabSelectionEditorIsHidden();
+        onView(allOf(withParent(withId(R.id.compositor_view_holder)), withId(R.id.tab_list_view)))
+                .check(TabCountAssertion.havingTabCount(2));
+        onView(isRoot()).check(waitForView(withText("2 tabs grouped")));
+    }
+
+    private void enterTabGroupManualSelection(ChromeTabbedActivity cta) {
+        MenuUtils.invokeCustomMenuActionSync(
+                InstrumentationRegistry.getInstrumentation(), cta, R.id.menu_group_tabs);
+    }
+
     private void switchTabModel(boolean isIncognito) {
         assertTrue(isIncognito !=
                 mActivityTestRule.getActivity().getTabModelSelector().isIncognitoSelected());
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java
index c7e8732..b8c1d320 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java
@@ -61,9 +61,10 @@
         if (mRenderer != null) return;
         assert mTileGroup == null;
 
-        boolean isIncognito = mActivity.getTabModelSelector().isIncognitoSelected();
-        Profile profile = isIncognito ? Profile.getLastUsedProfile().getOffTheRecordProfile()
-                                      : Profile.getLastUsedProfile();
+        // TODO(https://crbug.com/1041781): Use the current profile (i.e., regular profile or
+        // incognito profile) instead of always using regular profile. It is wrong and needs to be
+        // fixed.
+        Profile profile = Profile.getLastUsedRegularProfile();
         SuggestionsSource suggestionsSource =
                 SuggestionsDependencyFactory.getInstance().createSuggestionSource(profile);
         SuggestionsEventReporter eventReporter =
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java
index a6c2c4e8..824b083 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java
@@ -8,8 +8,6 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.os.Build;
-import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
-import android.support.v4.graphics.drawable.DrawableCompat;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
@@ -17,6 +15,8 @@
 
 import androidx.annotation.Nullable;
 import androidx.core.content.res.ResourcesCompat;
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.tab_ui.R;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppService.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppService.java
index 5a98b3f4..0b72f72 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppService.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser.payments;
 
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -27,9 +25,7 @@
     /** Prevent instantiation. */
     private PaymentAppService() {
         mFactories.add(new AutofillPaymentAppFactory());
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.SERVICE_WORKER_PAYMENT_APPS)) {
-            mFactories.add(new ServiceWorkerPaymentAppBridge());
-        }
+        mFactories.add(new PaymentAppServiceBridge());
         mFactories.add(new AndroidPaymentAppFactory());
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppServiceBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppServiceBridge.java
index 55ff3c7..0c2f8bc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppServiceBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppServiceBridge.java
@@ -4,68 +4,282 @@
 
 package org.chromium.chrome.browser.payments;
 
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.text.TextUtils;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.NativeMethods;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.components.payments.MethodStrings;
+import org.chromium.components.url_formatter.UrlFormatter;
 import org.chromium.content_public.browser.RenderFrameHost;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.payments.mojom.PaymentDetailsModifier;
+import org.chromium.payments.mojom.PaymentMethodData;
+import org.chromium.url.URI;
 
 import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Native bridge for finding payment apps.
  */
 public class PaymentAppServiceBridge implements PaymentAppFactoryInterface {
-    /* package */ PaymentAppServiceBridge() {}
+    private static final String TAG = "cr_PaymentAppService";
+    private static boolean sCanMakePaymentForTesting;
+    private final Set<String> mStandardizedPaymentMethods = new HashSet<>();
+
+    /* package */ PaymentAppServiceBridge() {
+        mStandardizedPaymentMethods.add(MethodStrings.BASIC_CARD);
+        mStandardizedPaymentMethods.add(MethodStrings.INTERLEDGER);
+        mStandardizedPaymentMethods.add(MethodStrings.PAYEE_CREDIT_TRANSFER);
+        mStandardizedPaymentMethods.add(MethodStrings.PAYER_CREDIT_TRANSFER);
+        mStandardizedPaymentMethods.add(MethodStrings.TOKENIZED_CARD);
+    }
+
+    /**
+     * Make canMakePayment() return true always for testing purpose.
+     *
+     * @param canMakePayment Indicates whether a SW payment app can make payment.
+     */
+    @VisibleForTesting
+    public static void setCanMakePaymentForTesting(boolean canMakePayment) {
+        sCanMakePaymentForTesting = canMakePayment;
+    }
 
     // PaymentAppFactoryInterface implementation.
     @Override
     public void create(PaymentAppFactoryDelegate delegate) {
-        assert (delegate.getParams().getRenderFrameHost().getLastCommittedURL().equals(
-                delegate.getParams().getPaymentRequestOrigin()));
+        assert delegate.getParams().getPaymentRequestOrigin().equals(
+                UrlFormatter.formatUrlForSecurityDisplay(
+                        delegate.getParams().getRenderFrameHost().getLastCommittedURL()));
+
         PaymentAppServiceCallback callback = new PaymentAppServiceCallback(delegate);
+
+        ByteBuffer[] serializedMethodData =
+                new ByteBuffer[delegate.getParams().getMethodData().values().size()];
+        int i = 0;
+        for (PaymentMethodData methodData : delegate.getParams().getMethodData().values()) {
+            serializedMethodData[i++] = methodData.serialize();
+        }
         PaymentAppServiceBridgeJni.get().create(delegate.getParams().getRenderFrameHost(),
-                delegate.getParams().getTopLevelOrigin(),
-                delegate.getParams()
-                        .getMethodData()
-                        .values()
-                        .stream()
-                        .map(data -> data.serialize())
-                        .toArray(ByteBuffer[] ::new),
+                delegate.getParams().getTopLevelOrigin(), serializedMethodData,
                 delegate.getParams().getMayCrawl(), callback);
     }
 
     /** Handles callbacks from native PaymentAppService and creates PaymentApps. */
     public class PaymentAppServiceCallback {
         private final PaymentAppFactoryDelegate mDelegate;
-        private boolean mCanMakePayment;
+        private boolean mPaymentHandlerWithMatchingMethodFound;
+        private int mNumberOfPendingCanMakePaymentEvents;
+        private boolean mDoneCreatingPaymentApps;
 
         private PaymentAppServiceCallback(PaymentAppFactoryDelegate delegate) {
             mDelegate = delegate;
-            mCanMakePayment = false;
         }
 
+        /** Called when an installed payment handler is found. */
         @CalledByNative("PaymentAppServiceCallback")
-        private void onPaymentAppCreated() {
+        private void onInstalledPaymentHandlerFound(long registrationId, String scope,
+                @Nullable String name, @Nullable String userHint, String origin,
+                @Nullable Bitmap icon, String[] methodNameArray, boolean explicitlyVerified,
+                Object[] capabilities, String[] preferredRelatedApplications,
+                Object supportedDelegations) {
             ThreadUtils.assertOnUiThread();
-            mCanMakePayment = true;
-            // TODO(crbug.com/1063118): call mDelegate.onPaymentAppCreated().
+
+            WebContents webContents = mDelegate.getParams().getWebContents();
+            ChromeActivity activity = ChromeActivity.fromWebContents(webContents);
+
+            ServiceWorkerPaymentApp app = createInstalledServiceWorkerPaymentApp(webContents,
+                    registrationId, scope, name, userHint, origin, icon, methodNameArray,
+                    explicitlyVerified, (ServiceWorkerPaymentApp.Capabilities[]) capabilities,
+                    preferredRelatedApplications, (SupportedDelegations) supportedDelegations);
+            if (app == null) return;
+
+            mPaymentHandlerWithMatchingMethodFound = true;
+            mNumberOfPendingCanMakePaymentEvents++;
+
+            ServiceWorkerPaymentAppBridge.CanMakePaymentEventCallback canMakePaymentEventCallback =
+                    new ServiceWorkerPaymentAppBridge.CanMakePaymentEventCallback() {
+                        @Override
+                        public void onCanMakePaymentEventResponse(String errorMessage,
+                                boolean canMakePayment, boolean readyForMinimalUI,
+                                @Nullable String accountBalance) {
+                            if (canMakePayment) mDelegate.onPaymentAppCreated(app);
+                            if (!TextUtils.isEmpty(errorMessage)) {
+                                mDelegate.onPaymentAppCreationError(errorMessage);
+                            }
+                            app.setIsReadyForMinimalUI(readyForMinimalUI);
+                            app.setAccountBalance(accountBalance);
+
+                            if (--mNumberOfPendingCanMakePaymentEvents == 0
+                                    && mDoneCreatingPaymentApps) {
+                                notifyFinished();
+                            }
+                        }
+                    };
+
+            if (sCanMakePaymentForTesting || activity.getCurrentTabModel().isIncognito()
+                    || mStandardizedPaymentMethods.containsAll(Arrays.asList(methodNameArray))
+                    || !explicitlyVerified) {
+                canMakePaymentEventCallback.onCanMakePaymentEventResponse(/*errorMessage=*/null,
+                        /*canMakePayment=*/true,
+                        /*readyForMinimalUI=*/false, /*accountBalance=*/null);
+                return;
+            }
+
+            Set<PaymentMethodData> supportedRequestedMethodData = new HashSet<>();
+            for (String methodName : methodNameArray) {
+                if (mDelegate.getParams().getMethodData().containsKey(methodName)) {
+                    supportedRequestedMethodData.add(
+                            mDelegate.getParams().getMethodData().get(methodName));
+                }
+            }
+
+            Set<PaymentDetailsModifier> supportedRequestedModifiers = new HashSet<>();
+            for (String methodName : methodNameArray) {
+                if (mDelegate.getParams().getModifiers().containsKey(methodName)) {
+                    supportedRequestedModifiers.add(
+                            mDelegate.getParams().getModifiers().get(methodName));
+                }
+            }
+
+            ServiceWorkerPaymentAppBridge.fireCanMakePaymentEvent(webContents, registrationId,
+                    scope, mDelegate.getParams().getId(), mDelegate.getParams().getTopLevelOrigin(),
+                    mDelegate.getParams().getPaymentRequestOrigin(),
+                    supportedRequestedMethodData.toArray(new PaymentMethodData[0]),
+                    supportedRequestedModifiers.toArray(new PaymentDetailsModifier[0]),
+                    ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_PAYMENTS_MINIMAL_UI)
+                            ? mDelegate.getParams().getTotalAmountCurrency()
+                            : null,
+                    canMakePaymentEventCallback);
         }
 
+        /** Called when an installable payment handler is found. */
+        @CalledByNative("PaymentAppServiceCallback")
+        private void onInstallablePaymentHandlerFound(@Nullable String name, String swUrl,
+                String scope, boolean useCache, @Nullable Bitmap icon, String methodName,
+                String[] preferredRelatedApplications, Object supportedDelegations) {
+            ThreadUtils.assertOnUiThread();
+
+            ServiceWorkerPaymentApp installableApp = createInstallableServiceWorkerPaymentApp(
+                    mDelegate.getParams().getWebContents(), name, swUrl, scope, useCache, icon,
+                    methodName, preferredRelatedApplications,
+                    (SupportedDelegations) supportedDelegations);
+
+            if (installableApp == null) return;
+            mDelegate.onPaymentAppCreated(installableApp);
+            mPaymentHandlerWithMatchingMethodFound = true;
+            if (mNumberOfPendingCanMakePaymentEvents == 0 && mDoneCreatingPaymentApps) {
+                notifyFinished();
+            }
+        }
+
+        /**
+         * Called when an error has occurred.
+         * @param errorMessage Developer facing error message.
+         */
         @CalledByNative("PaymentAppServiceCallback")
         private void onPaymentAppCreationError(String errorMessage) {
             ThreadUtils.assertOnUiThread();
             mDelegate.onPaymentAppCreationError(errorMessage);
         }
 
-        // Expect to be called exactly once
+        /**
+         * Called when the factory is finished creating payment apps. Expects to be called exactly
+         * once and after all onPaymentAppCreated() calls.
+         */
         @CalledByNative("PaymentAppServiceCallback")
         private void onDoneCreatingPaymentApps() {
             ThreadUtils.assertOnUiThread();
-            mDelegate.onCanMakePaymentCalculated(mCanMakePayment);
+            mDoneCreatingPaymentApps = true;
+            if (mNumberOfPendingCanMakePaymentEvents == 0) {
+                notifyFinished();
+            }
+        }
+
+        /**
+         * Signal completion of payment app lookup.
+         */
+        private void notifyFinished() {
+            assert mDoneCreatingPaymentApps;
+            assert mNumberOfPendingCanMakePaymentEvents == 0;
+            mDelegate.onCanMakePaymentCalculated(mPaymentHandlerWithMatchingMethodFound);
             mDelegate.onDoneCreatingPaymentApps(PaymentAppServiceBridge.this);
         }
     }
 
+    @CalledByNative
+    private static Object[] createCapabilities(int count) {
+        return new ServiceWorkerPaymentApp.Capabilities[count];
+    }
+
+    @CalledByNative
+    private static void addCapabilities(
+            Object[] capabilities, int index, int[] supportedCardNetworks) {
+        assert index < capabilities.length;
+        capabilities[index] = new ServiceWorkerPaymentApp.Capabilities(supportedCardNetworks);
+    }
+
+    @CalledByNative
+    private static Object createSupportedDelegations(
+            boolean shippingAddress, boolean payerName, boolean payerPhone, boolean payerEmail) {
+        return new SupportedDelegations(shippingAddress, payerName, payerPhone, payerEmail);
+    }
+
+    private static @Nullable ServiceWorkerPaymentApp createInstalledServiceWorkerPaymentApp(
+            WebContents webContents, long registrationId, String scope, @Nullable String name,
+            @Nullable String userHint, String origin, @Nullable Bitmap icon,
+            String[] methodNameArray, boolean explicitlyVerified,
+            ServiceWorkerPaymentApp.Capabilities[] capabilities,
+            String[] preferredRelatedApplications, SupportedDelegations supportedDelegations) {
+        ChromeActivity activity = ChromeActivity.fromWebContents(webContents);
+        if (activity == null) return null;
+
+        URI scopeUri = UriUtils.parseUriFromString(scope);
+        if (scopeUri == null) {
+            Log.e(TAG, "%s service worker scope is not a valid URI", scope);
+            return null;
+        }
+
+        return new ServiceWorkerPaymentApp(webContents, registrationId, scopeUri, name, userHint,
+                origin, icon == null ? null : new BitmapDrawable(activity.getResources(), icon),
+                methodNameArray, capabilities, preferredRelatedApplications, supportedDelegations);
+    }
+
+    private static @Nullable ServiceWorkerPaymentApp createInstallableServiceWorkerPaymentApp(
+            WebContents webContents, @Nullable String name, String swUrl, String scope,
+            boolean useCache, @Nullable Bitmap icon, String methodName,
+            String[] preferredRelatedApplications, SupportedDelegations supportedDelegations) {
+        Context context = ChromeActivity.fromWebContents(webContents);
+        if (context == null) return null;
+        URI swUri = UriUtils.parseUriFromString(swUrl);
+        if (swUri == null) {
+            Log.e(TAG, "%s service worker installation url is not a valid URI", swUrl);
+            return null;
+        }
+
+        URI scopeUri = UriUtils.parseUriFromString(scope);
+        if (scopeUri == null) {
+            Log.e(TAG, "%s service worker scope is not a valid URI", scope);
+            return null;
+        }
+
+        return new ServiceWorkerPaymentApp(webContents, name, scopeUri.getHost(), swUri, scopeUri,
+                useCache, icon == null ? null : new BitmapDrawable(context.getResources(), icon),
+                methodName, preferredRelatedApplications, supportedDelegations);
+    }
+
     @NativeMethods
     /* package */ interface Natives {
         void create(RenderFrameHost initiatorRenderFrameHost, String topOrigin,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
index fe255f4..c885c0f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
@@ -4,30 +4,24 @@
 
 package org.chromium.chrome.browser.payments;
 
-import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
 import android.text.TextUtils;
 import android.util.Pair;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNIAdditionalImport;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.base.task.PostTask;
-import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.components.payments.MethodStrings;
 import org.chromium.components.payments.PayerData;
 import org.chromium.components.payments.PaymentHandlerHost;
 import org.chromium.content_public.browser.NavigationHandle;
-import org.chromium.content_public.browser.RenderFrameHost;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.payments.mojom.PaymentAddress;
@@ -38,12 +32,9 @@
 import org.chromium.payments.mojom.PaymentMethodData;
 import org.chromium.payments.mojom.PaymentOptions;
 import org.chromium.payments.mojom.PaymentShippingOption;
-import org.chromium.url.Origin;
 import org.chromium.url.URI;
 
-import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -52,10 +43,8 @@
  * Native bridge for interacting with service worker based payment apps.
  */
 @JNIAdditionalImport({PaymentApp.class})
-public class ServiceWorkerPaymentAppBridge implements PaymentAppFactoryInterface {
+public class ServiceWorkerPaymentAppBridge {
     private static final String TAG = "SWPaymentApp";
-    private static boolean sCanMakePaymentForTesting;
-    private final Set<String> mStandardizedPaymentMethods = new HashSet<>();
 
     /** The interface for checking whether there is an installed SW payment app. */
     public static interface HasServiceWorkerPaymentAppsCallback {
@@ -77,161 +66,11 @@
         public void onGetServiceWorkerPaymentAppsInfo(Map<String, Pair<String, Bitmap>> appsInfo);
     }
 
-    /* package */ ServiceWorkerPaymentAppBridge() {
-        mStandardizedPaymentMethods.add(MethodStrings.BASIC_CARD);
-        mStandardizedPaymentMethods.add(MethodStrings.INTERLEDGER);
-        mStandardizedPaymentMethods.add(MethodStrings.PAYEE_CREDIT_TRANSFER);
-        mStandardizedPaymentMethods.add(MethodStrings.PAYER_CREDIT_TRANSFER);
-        mStandardizedPaymentMethods.add(MethodStrings.TOKENIZED_CARD);
-    }
-
-    // PaymentAppFactoryInterface implementation:
-    @Override
-    public void create(PaymentAppFactoryDelegate delegate) {
-        PaymentHandlerFinder finder = new PaymentHandlerFinder(delegate);
-        ServiceWorkerPaymentAppBridgeJni.get().getAllPaymentApps(
-                delegate.getParams().getPaymentRequestSecurityOrigin(),
-                delegate.getParams().getRenderFrameHost(),
-                delegate.getParams().getMethodData().values().toArray(
-                        new PaymentMethodData[delegate.getParams().getMethodData().size()]),
-                delegate.getParams().getMayCrawl(),
-                /*callback=*/finder);
-    }
-
-    /** Finds the payment handlers and checks their "canmakepayment" response. */
-    public class PaymentHandlerFinder {
-        private final PaymentAppFactoryDelegate mDelegate;
-        private int mNumberOfPendingCanMakePaymentEvents;
-        private boolean mAreAllPaymentAppsCreated;
-
+    /** The interface for checking "canmakepayment" response in an installed SW payment app. */
+    public static interface CanMakePaymentEventCallback {
         /**
-         * True if at least one payment handler with matching payment methods is found, regardless
-         * of its reply to the "canmakepayment" event.
-         */
-        private boolean mPaymentHandlerWithMatchingMethodFound;
-
-        private PaymentHandlerFinder(PaymentAppFactoryDelegate delegate) {
-            mDelegate = delegate;
-        }
-
-        /** Called when an installed payment handler is found. */
-        @CalledByNative("PaymentHandlerFinder")
-        private void onInstalledPaymentHandlerFound(long registrationId, String scope,
-                @Nullable String name, @Nullable String userHint, String origin,
-                @Nullable Bitmap icon, String[] methodNameArray, boolean explicitlyVerified,
-                Object[] capabilities, String[] preferredRelatedApplications,
-                Object supportedDelegations) {
-            ThreadUtils.assertOnUiThread();
-
-            WebContents webContents = mDelegate.getParams().getWebContents();
-            ChromeActivity activity = ChromeActivity.fromWebContents(webContents);
-            if (activity == null) return;
-
-            URI scopeUri = UriUtils.parseUriFromString(scope);
-            if (scopeUri == null) {
-                Log.e(TAG, "%s service worker scope is not a valid URI", scope);
-                return;
-            }
-
-            ServiceWorkerPaymentApp app = new ServiceWorkerPaymentApp(webContents, registrationId,
-                    scopeUri, name, userHint, origin,
-                    icon == null ? null : new BitmapDrawable(activity.getResources(), icon),
-                    methodNameArray, (ServiceWorkerPaymentApp.Capabilities[]) capabilities,
-                    preferredRelatedApplications, (SupportedDelegations) supportedDelegations);
-            mPaymentHandlerWithMatchingMethodFound = true;
-            mNumberOfPendingCanMakePaymentEvents++;
-
-            if (sCanMakePaymentForTesting || activity.getCurrentTabModel().isIncognito()
-                    || mStandardizedPaymentMethods.containsAll(Arrays.asList(methodNameArray))
-                    || !explicitlyVerified) {
-                onCanMakePaymentEventResponse(app, /*errorMessage=*/null, /*canMakePayment=*/true,
-                        /*readyForMinimalUI=*/false, /*accountBalance=*/null);
-                return;
-            }
-
-            Set<PaymentMethodData> supportedRequestedMethodData = new HashSet<>();
-            for (String methodName : methodNameArray) {
-                if (mDelegate.getParams().getMethodData().containsKey(methodName)) {
-                    supportedRequestedMethodData.add(
-                            mDelegate.getParams().getMethodData().get(methodName));
-                }
-            }
-
-            Set<PaymentDetailsModifier> supportedRequestedModifiers = new HashSet<>();
-            for (String methodName : methodNameArray) {
-                if (mDelegate.getParams().getModifiers().containsKey(methodName)) {
-                    supportedRequestedModifiers.add(
-                            mDelegate.getParams().getModifiers().get(methodName));
-                }
-            }
-
-            ServiceWorkerPaymentAppBridgeJni.get().fireCanMakePaymentEvent(webContents,
-                    registrationId, scope, mDelegate.getParams().getId(),
-                    mDelegate.getParams().getTopLevelOrigin(),
-                    mDelegate.getParams().getPaymentRequestOrigin(),
-                    supportedRequestedMethodData.toArray(new PaymentMethodData[0]),
-                    supportedRequestedModifiers.toArray(new PaymentDetailsModifier[0]),
-                    ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_PAYMENTS_MINIMAL_UI)
-                            ? mDelegate.getParams().getTotalAmountCurrency()
-                            : null,
-                    /*callback=*/this, app);
-        }
-
-        /** Called when an installable payment handler is found. */
-        @CalledByNative("PaymentHandlerFinder")
-        private void onInstallablePaymentHandlerFound(@Nullable String name, String swUrl,
-                String scope, boolean useCache, @Nullable Bitmap icon, String methodName,
-                String[] preferredRelatedApplications, Object supportedDelegations) {
-            ThreadUtils.assertOnUiThread();
-
-            WebContents webContents = mDelegate.getParams().getWebContents();
-            Context context = ChromeActivity.fromWebContents(webContents);
-            if (context == null) return;
-            URI swUri = UriUtils.parseUriFromString(swUrl);
-            if (swUri == null) {
-                Log.e(TAG, "%s service worker installation url is not a valid URI", swUrl);
-                return;
-            }
-
-            URI scopeUri = UriUtils.parseUriFromString(scope);
-            if (scopeUri == null) {
-                Log.e(TAG, "%s service worker scope is not a valid URI", scope);
-                return;
-            }
-
-            mDelegate.onPaymentAppCreated(new ServiceWorkerPaymentApp(webContents, name,
-                    scopeUri.getHost(), swUri, scopeUri, useCache,
-                    icon == null ? null : new BitmapDrawable(context.getResources(), icon),
-                    methodName, preferredRelatedApplications,
-                    (SupportedDelegations) supportedDelegations));
-            mPaymentHandlerWithMatchingMethodFound = true;
-            if (mNumberOfPendingCanMakePaymentEvents == 0 && mAreAllPaymentAppsCreated) {
-                notifyFinished();
-            }
-        }
-
-        /**
-         * Called when an error has occurred.
-         * @param errorMessage Developer facing error message.
-         */
-        @CalledByNative("PaymentHandlerFinder")
-        private void onGetPaymentAppsError(String errorMessage) {
-            ThreadUtils.assertOnUiThread();
-            mDelegate.onPaymentAppCreationError(errorMessage);
-        }
-
-        /** Called when the factory is finished creating payment apps. */
-        @CalledByNative("PaymentHandlerFinder")
-        private void onAllPaymentAppsCreated() {
-            ThreadUtils.assertOnUiThread();
-            mAreAllPaymentAppsCreated = true;
-            if (mNumberOfPendingCanMakePaymentEvents == 0 && mAreAllPaymentAppsCreated) {
-                notifyFinished();
-            }
-        }
-
-        /**
-         * Called when a service worker responds to the "canmakepayment" event.
+         * Called to return "canmakepayment" result.
+         *
          * @param app The service worker that has responded to the "canmakepayment" event.
          * @param errorMessage An optional error message about any problems encountered while firing
          * the "canmakepayment" event.
@@ -239,26 +78,12 @@
          * @param readyForMinimalUI Whether minimal UI should be used.
          * @param accountBalance The account balance to display in the minimal UI.
          */
-        @CalledByNative("PaymentHandlerFinder")
-        private void onCanMakePaymentEventResponse(ServiceWorkerPaymentApp app, String errorMessage,
-                boolean canMakePayment, boolean readyForMinimalUI,
-                @Nullable String accountBalance) {
-            if (canMakePayment) mDelegate.onPaymentAppCreated(app);
-            if (!TextUtils.isEmpty(errorMessage)) mDelegate.onPaymentAppCreationError(errorMessage);
-            app.setIsReadyForMinimalUI(readyForMinimalUI);
-            app.setAccountBalance(accountBalance);
-
-            if (--mNumberOfPendingCanMakePaymentEvents == 0 && mAreAllPaymentAppsCreated) {
-                notifyFinished();
-            }
-        }
-
-        private void notifyFinished() {
-            mDelegate.onCanMakePaymentCalculated(mPaymentHandlerWithMatchingMethodFound);
-            mDelegate.onDoneCreatingPaymentApps(ServiceWorkerPaymentAppBridge.this);
-        }
+        public void onCanMakePaymentEventResponse(String errorMessage, boolean canMakePayment,
+                boolean readyForMinimalUI, @Nullable String accountBalance);
     }
 
+    /* package */ ServiceWorkerPaymentAppBridge() {}
+
     /**
      * Checks whether there is a installed SW payment app.
      *
@@ -308,7 +133,20 @@
      */
     @VisibleForTesting
     public static void setCanMakePaymentForTesting(boolean canMakePayment) {
-        sCanMakePaymentForTesting = canMakePayment;
+        PaymentAppServiceBridge.setCanMakePaymentForTesting(canMakePayment);
+    }
+
+    /**
+     * Fires a "canmakepayment" event for the payment app with the registration ID.
+     */
+    public static void fireCanMakePaymentEvent(WebContents webContents, long registrationId,
+            String serviceWorkerScope, String paymentRequestId, String topOrigin,
+            String paymentRequestOrigin, PaymentMethodData[] methodData,
+            PaymentDetailsModifier[] modifiers, @Nullable String currency,
+            CanMakePaymentEventCallback callback) {
+        ServiceWorkerPaymentAppBridgeJni.get().fireCanMakePaymentEvent(webContents, registrationId,
+                serviceWorkerScope, paymentRequestId, topOrigin, paymentRequestOrigin, methodData,
+                modifiers, currency, callback);
     }
 
     /**
@@ -469,6 +307,24 @@
                 swScope.toString());
     }
 
+    /**
+     * Called when a service worker responds to the "canmakepayment" event.
+     * @param app The service worker that has responded to the "canmakepayment" event.
+     * @param errorMessage An optional error message about any problems encountered while firing
+     * the "canmakepayment" event.
+     * @param canMakePayment Whether payments can be made.
+     * @param readyForMinimalUI Whether minimal UI should be used.
+     * @param accountBalance The account balance to display in the minimal UI.
+     */
+    @CalledByNative
+    private static void onCanMakePaymentEventResponse(CanMakePaymentEventCallback callback,
+            String errorMessage, boolean canMakePayment, boolean readyForMinimalUI,
+            @Nullable String accountBalance) {
+        ThreadUtils.assertOnUiThread();
+        callback.onCanMakePaymentEventResponse(
+                errorMessage, canMakePayment, readyForMinimalUI, accountBalance);
+    }
+
     @CalledByNative
     private static String getSupportedMethodFromMethodData(PaymentMethodData data) {
         return data.supportedMethod;
@@ -566,24 +422,6 @@
     }
 
     @CalledByNative
-    private static Object[] createCapabilities(int count) {
-        return new ServiceWorkerPaymentApp.Capabilities[count];
-    }
-
-    @CalledByNative
-    private static Object createSupportedDelegations(
-            boolean shippingAddress, boolean payerName, boolean payerPhone, boolean payerEmail) {
-        return new SupportedDelegations(shippingAddress, payerName, payerPhone, payerEmail);
-    }
-
-    @CalledByNative
-    private static void addCapabilities(
-            Object[] capabilities, int index, int[] supportedCardNetworks) {
-        assert index < capabilities.length;
-        capabilities[index] = new ServiceWorkerPaymentApp.Capabilities(supportedCardNetworks);
-    }
-
-    @CalledByNative
     private static void onHasServiceWorkerPaymentApps(
             HasServiceWorkerPaymentAppsCallback callback, boolean hasPaymentApps) {
         ThreadUtils.assertOnUiThread();
@@ -665,9 +503,6 @@
 
     @NativeMethods
     interface Natives {
-        void getAllPaymentApps(Origin merchantOrigin, RenderFrameHost initiatorRenderFrameHost,
-                PaymentMethodData[] methodData, boolean mayCrawlForInstallablePaymentApps,
-                PaymentHandlerFinder callback);
         void hasServiceWorkerPaymentApps(HasServiceWorkerPaymentAppsCallback callback);
         void getServiceWorkerPaymentAppsInfo(GetServiceWorkerPaymentAppsInfoCallback callback);
         void invokePaymentApp(WebContents webContents, long registrationId,
@@ -691,8 +526,8 @@
         void fireCanMakePaymentEvent(WebContents webContents, long registrationId,
                 String serviceWorkerScope, String paymentRequestId, String topOrigin,
                 String paymentRequestOrigin, PaymentMethodData[] methodData,
-                PaymentDetailsModifier[] modifiers, String currency, PaymentHandlerFinder callback,
-                ServiceWorkerPaymentApp app);
+                PaymentDetailsModifier[] modifiers, String currency,
+                CanMakePaymentEventCallback callback);
         void onClosingPaymentAppWindow(WebContents webContents, int reason);
         long getSourceIdForPaymentAppFromScope(String swScope);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/AuthenticatorNavigationInterceptorTabHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/AuthenticatorNavigationInterceptorTabHelper.java
new file mode 100644
index 0000000..9a6dddf6
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/AuthenticatorNavigationInterceptorTabHelper.java
@@ -0,0 +1,18 @@
+// 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.
+
+package org.chromium.chrome.browser.tab;
+
+/**
+ * Temporary wrapper to isolate downstream from the exact API surfaces used to get an
+ * AuthenticatorNavigationInterceptor instance from a Tab while those API surfaces are being
+ * refactored.
+ * TODO(blundell): Delete this once InterceptNavigationDelegateTabHelper has landed and
+ * InterceptNavigationDelegateImpl has been componentized in Chromium.
+ */
+public class AuthenticatorNavigationInterceptorTabHelper {
+    public static AuthenticatorNavigationInterceptor getInterceptorForTab(Tab tab) {
+        return InterceptNavigationDelegateImpl.get(tab).getAuthenticatorNavigationInterceptor();
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
index ccfdaec..427795f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
@@ -214,6 +214,7 @@
     }
 
     @Test
+    @DisabledTest(message = "https://crbug.com/888129")
     @SmallTest
     @Feature({"NewTabPage", "FeedNewTabPage", "RenderTest"})
     @ParameterAnnotations.UseMethodParameter(InterestFeedParams.class)
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index be5b00f..4463820 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-84.0.4106.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-84.0.4108.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index afb45040..144c126 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -1710,6 +1710,223 @@
     SSID
   </message>
 
+  <!-- Settings Internet Page-->
+  <message name="IDS_SETTINGS_INTERNET_CONFIG" desc="Title for the network configuration dialog.">
+    Configure network
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_CONFIG_SHARE" desc="Settings > Internet > Network config: Label for setting allowing other device users to use the network configuration.">
+    Allow other users of this device to use this network
+  </message>
+  <message name="IDS_SETTINGS_HIDDEN_NETWORK_WARNING" desc="Settings > Internet > Network config: Text shown when auto connect to the WiFi network is enabled, warning about the dangers of auto-connecting to hidden networks.">
+    Automatically connecting to a hidden network allows others to see your device and some network settings, and is not recommended.
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_CONFIG_SAVE_CREDENTIALS" desc="Settings > Internet > Network config: Label for the setting to save identity and password.">
+    Save identity and password
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_CA_USE_DEFAULT" desc="Settings > Internet > Network config: Label for selecting the default Certificate Authority for an EAP network.">
+    Default
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_CA_DO_NOT_CHECK" desc="Settings > Internet > Network config: Label for selecting that no Certificate Authority should be used for an EAP network.">
+    Do not check
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_NO_USER_CERT" desc="Settings > Internet > Network config: Label for selecting that no User Certificate should be used when connected to an OpenVPN network.">
+    No user certificate
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_CERTIFICATE_NAME" desc="Settings > Internet > Network config: Formatting string for network certificate names.">
+    <ph name="ISSUED_BY">$1<ex>Google Inc</ex></ph> [<ph name="ISSUED_TO">$2<ex>John Doe</ex></ph>]
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_CERTIFICATE_NAME_HARDWARE_BACKED" desc="Settings > Internet > Network configuration: Formatting string for network certificate names that are hardware backed.">
+    <ph name="ISSUED_BY">$1<ex>Google Inc</ex></ph> [<ph name="ISSUED_TO">$2<ex>John Doe</ex></ph>] (hardware-backed)
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_CERTIFICATE_NONE_INSTALLED" desc="Settings > Internet > Network configuration: Label when no certificates are installed.">
+    None installed
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_REQUIRE_HARDWARE_BACKED" desc="In the network connect dialog, when creating a VPN or enterprise Wi-Fi connection, error message to display when a non hardware-backed user certificate is selected.">
+    User certificate must be hardware-backed.
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_ACCESS_POINT" desc="Settings > Internet > Network details: Access Point section label.">
+    Access Point
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_NAMESERVERS" desc="Settings > Network: Name servers label.">
+    Name servers
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_NAMESERVERS_AUTOMATIC" desc="Settings > Internet > Network details: Label for setting automatic name servers.">
+    Automatic name servers
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_NAMESERVERS_CUSTOM" desc="Settings > Internet > Network details: Label for setting custom name servers.">
+    Custom name servers
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_NAMESERVERS_GOOGLE" desc="Settings > Internet > Network details: Label for setting Google name servers.">
+    Google name servers
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_NAMESERVERS_INPUT_ACCESSIBILITY_LABEL" desc="Settings > Internet > Network details: Accessibility only label for custom nameserver input box.">
+    Custom nameserver <ph name="INPUT_INDEX">$1<ex>1</ex></ph>
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_WPAD" desc="Settings > Internet > Network details: Web Proxy Auto Discovery (WPAD) label.">
+    Web Proxy Auto Discovery URL:
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_WPAD_NONE" desc="Settings > Internet > Network details: Value for Web Proxy Auto Discovery when no URL is provided.">
+    Not provided
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_CHOOSE_MOBILE" desc="Settings > Internet > Network details: Label for control to choose a mobile data network.">
+    Mobile data network
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_CELLULAR_SCAN" desc="Settings > Internet > Network details: Label for button to start a Cellular scan.">
+    Scan
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_CELLULAR_SCAN_COMPLETED" desc="Settings > Internet > Network details: Status text when a scan has completed.">
+    Scan completed
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_CELLULAR_SCAN_CONNECTED_HELP" desc="Settings > Internet > Network details: Help message for scan button when network is connected.">
+    Disconnect to enable scanning
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_CELLULAR_SCANNING" desc="Settings > Internet > Network details: Status text when a scan is in progress.">
+    Scanning...
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_CELLULAR_NO_NETWORKS" desc="Settings > Internet > Network details: Dropdown text when no cellular networks were found after a scan.">
+    No networks
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_ENFORCED_POLICY" desc="Settings > Internet > Network details: Text to show when a network proxy is policy enforced.">
+    This proxy is enforced by your administrator
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_ALLOW_SHARED" desc="Settings > Internet > Network details: Text next to the checkbox for allowing proxy settings for shared networks.">
+    Allow proxies for shared networks
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_ALLOW_SHARED_ENABLE_WARNING_TITLE" desc="Settings > Internet > Network details: Title for warning dialog when changing the allow shared proxies setting is enabled.">
+    Allow proxies for shared networks
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_ALLOW_SHARED_DISABLE_WARNING_TITLE" desc="Settings > Internet > Network details: Title for warning dialog when changing the allow shared proxies setting is disabled.">
+    Disallow proxies for shared networks
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_ALLOW_SHARED_WARNING_MESSAGE" desc="Settings > Internet > Network details: Warning message when changing the allow shared proxies setting.">
+    Changing this setting will affect all shared networks
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_TYPE_DIRECT" desc="Radio used to tell it to just connect directly, not use a proxy.">
+    Direct Internet connection
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_TYPE_MANUAL" desc="Radio used to tell it to configure manually.">
+    Manual proxy configuration
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_TYPE_PAC" desc="Radio to select configuring from a URL.">
+    Automatic proxy configuration
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_TYPE_WPAD" desc="Label for the checkbox that controls whether to use an autoconfiguration URL.">
+    Web proxy autodiscovery
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_USE_SAME" desc="Settings > Internet > Network details: Label for checkbox indicating that a network proxy uses the same value for all protocols.">
+    Use the same proxy for all protocols
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_EXCEPTION_INPUT_ACCESSIBILITY_LABEL" desc="Settings > Internet > Accessibility only label for input field to add proxy exceptions.">
+    Host or domain to exclude
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_EXCEPTION_LIST" desc="Settings > Internet > Network details: Label for list of network proxy exceptions.">
+    Do not use the proxy settings for these hosts and domains:
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_EXCEPT_REMOVE_ACCESSIBILITY_LABEL" desc="Settings > Internet > Network details: Accessibility only label for button to remove proxy exception.">
+    Remove exception for <ph name="HOST_NAME">$1<ex>example.com</ex></ph>
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_ADD_EXCEPTION" desc="Settings > Internet > Network details: Label for button to add a network proxy exception.">
+    Add exception
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_CONNECTION_TYPE" desc="Settings > Internet > Network details: Label for proxy connection type dropdown.">
+    Connection type
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_CONNECTION_TYPE_DIALOG" desc="Label for proxy connection type dropdown in the network details dialog used in the login screen.">
+    Proxy connection type
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_AUTO_CONFIG" desc="Settings > Internet > Network details: Label for proxy autoconfiguration URL input.">
+    Autoconfiguration URL:
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_PROXY" desc="Settings > Internet > Network details: Label for all proxies.">
+    Proxy
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_HOST_INPUT_ACCESSIBILITY_LABEL" desc="Settings > Internet > Network details: Accessibility only label for proxy host input field.">
+    <ph name="INPUT_LABEL">$1<ex>HTTP Proxy</ex></ph> - Host
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_HTTP_PROXY" desc="Settings > Internet > Network details: Label for HTTP proxy.">
+    HTTP Proxy
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_SHTTP_PROXY" desc="Settings > Internet > Network details: Label for SHTTP proxy.">
+    Secure HTTP Proxy
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_FTP_PROXY" desc="Settings > Internet > Network details: Label for FTP proxy.">
+    FTP Proxy
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_SOCKS_HOST" desc="Settings > Internet > Network details: Label for SOCKS host proxy.">
+    SOCKS Host
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_PORT" desc="Settings > Internet > Network details: Label for proxy port input.">
+    Port
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_PORT_INPUT_ACCESSIBILITY_LABEL" desc="Settings > Internet > Network details: Accessibility only label for proxy port number input field.">
+    <ph name="INPUT_LABEL">$1<ex>HTTP Proxy</ex></ph> - Port
+  </message>
+
+  <!-- Settings > Internet > SIM lock/unlock dialog -->
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_CHANGE_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for buton to change the PIN.">
+    Change PIN
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_CARD_MISSING" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when SIM card is missing.">
+    Missing SIM card
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_CARD_LOCKED" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when SIM card is locked.">
+    SIM card is locked
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_LOCK_ENABLE" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for checkbox to enable SIM card locking.">
+    Enable SIM card locking (require PIN to use mobile data)
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_BUTTON_CHANGE" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for dialog button to change a PIN.">
+    Change
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_BUTTON_ENTER" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for dialog button to enter a PIN; note that in this context, 'Enter' refers to the act of entering a PIN, not the name of a key on the keyboard." meaning="Enter a PIN into the device. [Verb]">
+    Enter
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_BUTTON_UNLOCK" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for button to unlock the SIM card.">
+    Unlock
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ENTER_PIN_TITLE" desc="Settings > Internet > Network details > Lock/unlock SIM card: Title for enter PIN dialog.">
+    Enter SIM PIN
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_CHANGE_PIN_TITLE" desc="Settings > Internet > Network details > Lock/unlock SIM card: Title for change PIN dialog.">
+    Change SIM PIN
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_LOCKED_TITLE" desc="Settings > Internet > Network details > Lock/unlock SIM card: Title for SIM locked dialog.">
+    SIM Card is locked
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_LOCKED_WARNING" desc="Settings > Internet > Network details > Lock/unlock SIM card: Warning for SIM locked dialog when PUK is required.">
+    Your SIM card will be permanently disabled if you cannot enter the correct PIN Unlock Key.
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ENTER_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for entering a PIN.">
+    Enter PIN
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ENTER_OLD_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for entering an old (current) PIN.">
+    Enter old PIN
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ENTER_NEW_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for entering a new PIN.">
+    Enter new PIN
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_RE_ENTER_NEW_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for re-entering a new PIN.">
+    Re-enter new PIN
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ENTER_PUK" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for entering a PUK.">
+    Enter PIN Unlock Key
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ERROR_INCORRECT_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when an incorrect PIN was entered.">
+    Incorrect PIN. Retries left: <ph name="RETRIES">$1<ex>3</ex></ph>.
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ERROR_INCORRECT_PUK" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when an incorrect PUK was entered.">
+    Incorrect PUK. Retries left: <ph name="RETRIES">$1<ex>3</ex></ph>.
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ERROR_INVALID_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when an invalid PIN was entered.">
+    Invalid PIN. Retries left: <ph name="RETRIES">$1<ex>3</ex></ph>.
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ERROR_INVALID_PUK" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when an invalid PUK was entered.">
+    Invalid PUK. Retries left: <ph name="RETRIES">$1<ex>3</ex></ph>.
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ERROR_PIN_MISMATCH" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when PIN values do not match.">
+    PIN values do not match.
+  </message>
+
   <!-- Strings shown within Tether notifications -->
   <message name="IDS_TETHER_NOTIFICATION_CONNECTION_FAILED_TITLE" desc="Title of the notification shown to the user when an attempt to tether to another device has failed.">
     Instant Tethering connection failed
@@ -4139,6 +4356,12 @@
   <message name="IDS_CROSTINI_UPGRADER_RESTORE_TITLE" desc="Title of the Crostini upgrader when the container upgrade fails and restore of a backup is in progress.">
     Restoring Linux
   </message>
+  <message name="IDS_CROSTINI_UPGRADER_RESTORE_SUCCEEDED_TITLE" desc="Title of the Crostini upgrader when the upgrade failed but the restore of a backup completed successfully.">
+    Restore complete
+  </message>
+  <message name="IDS_CROSTINI_UPGRADER_RESTORE_SUCCEEDED_MESSAGE" desc="Text shown by the Crostini upgrader when the upgrade failed but the restore of a backup completed successfully..">
+    Linux backup has been successfully restored.
+  </message>
   <message name="IDS_CROSTINI_UPGRADER_ERROR_TITLE" desc="Title of the Crostini upgrader when an error occurs.">
     Error upgrading Linux
   </message>
@@ -5009,4 +5232,27 @@
   <message name="IDS_POWERWASH_REQUEST_UNDEFINED_STATE_ERROR_MESSAGE_FOR_CROSTINI" desc="Message of the notification for user trying to start Crostini application while powerwash state is not determined yet">
     Please try to start Linux again in a few moments.
   </message>
+
+  <!-- PIN keyboard-->
+  <message name="IDS_SETTINGS_PEOPLE_CONFIGURE_PIN_WEAK_PIN" desc="Message shown below the title that warns the user they have entered a PIN that is easy to guess.">
+    PIN may be easy to guess
+  </message>
+  <message name="IDS_SETTINGS_PEOPLE_CONFIGURE_PIN_TOO_SHORT" desc="Message shown below the title that tells the user that the PIN they entered needs to be at least minimum digits long.">
+    PIN must be at least <ph name="MINIMUM">$1<ex>4</ex></ph> digits
+  </message>
+  <message name="IDS_SETTINGS_PEOPLE_CONFIGURE_PIN_TOO_LONG" desc="Message shown below the title that tells the user that the PIN they entered needs to be less than maximum digits long.">
+    PIN must be less than <ph name="MAXIMUM">$1<ex>0</ex></ph> digits
+  </message>
+  <message name="IDS_SETTINGS_PEOPLE_CONFIGURE_PIN_MISMATCHED" desc="Message shown below the title that tells the user they have entered two different PIN values.">
+    PINs do not match
+  </message>
+  <message name="IDS_SETTINGS_ACCOUNT_MANAGER_ACCOUNT_REMOVED_MESSAGE" desc="Notification message after account removal.">
+    <ph name="EMAIL">$1<ex>abcd@google.com</ex></ph> was removed from this device
+  </message>
+  <message name="IDS_SETTINGS_PEOPLE_PASSWORD_PROMPT_PASSWORD_LABEL" desc="An input box label that tells the user to enter their password in that input box.">
+    Password
+  </message>
+  <message name="IDS_SETTINGS_PEOPLE_PASSWORD_PROMPT_INVALID_PASSWORD" desc="Text on a password field that tells the user the password is incorrect.">
+    Invalid password
+  </message>
 </grit-part>
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index a6225d7..50731ea8 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -7,6 +7,17 @@
   <message name="IDS_SETTINGS_TURN_ON" desc="Label for turn on buttons.">
     Turn on
   </message>
+  <message name="IDS_SETTINGS_DEVICE_OFF" desc="In Settings pages, the label when a bluetooth, wifi, or mobile device is off (disabled)." meaning="Device is disabled.">
+    Off
+  </message>
+  <message name="IDS_SETTINGS_DEVICE_ON" desc="In Settings pages, the label when a bluetooth, wifi, or mobile device is on (enabled)." meaning="Device is enabled.">
+    On
+  </message>
+
+  <!--Main Page-->
+  <message name="IDS_SETTINGS_SECONDARY_USER_BANNER" desc="Banner displayed in settings page when the user is secondary in a multi-profile session.">
+    Some settings belonging to <ph name="PRIMARY_EMAIL">$1<ex>john@google.com</ex></ph> are being shared with you. These settings only affect your account when using multiple sign-in.
+  </message>
 
   <!-- About (OS Settings) -->
   <message name="IDS_OS_SETTINGS_ABOUT_PAGE_BUILD_DETAILS" desc="Label of the row button that clicks into Build details">
@@ -176,6 +187,9 @@
   <message name="IDS_OS_SETTINGS_WALLPAPER_CHECKBOX_LABEL" desc="Label for the checkbox which enables syncing of wallpaper across devices.">
     Wallpaper
   </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
+  </message>
   <message name="IDS_SETTINGS_ACCOUNT_MANAGER_SUBMENU_LABEL" desc="Label of Account Manager submenu in Settings page.">
     Google Accounts
   </message>
@@ -188,6 +202,27 @@
   <message name="IDS_SETTINGS_PEOPLE_MANAGE_OTHER_PEOPLE" desc="Label for the button that opens the multi-profile user manager.">
     Manage other people
   </message>
+  <message name="IDS_SETTINGS_ACCOUNT_MANAGER_EDUCATION_ACCOUNT" desc="Status label which indicates that specified account is a school/EDU account.">
+    School account
+  </message>
+  <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_POWER_BUTTON" desc="Text in the add fingerprint dialog telling users to place finger on the power button which is the sensor.">
+    Touch the power button with your finger
+  </message>
+    <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_POWER_BUTTON_ARIA_LABEL" desc="Aria label in the add fingerprint dialog telling users that the fingerprint sensor is in the power button. Only visible by screen reader software.">
+    The fingerprint sensor is in the power button. Touch it lightly with any finger.
+  </message>
+  <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_KEYBOARD" desc="Text in the add fingerprint dialog telling users to place finger on the sensor on the keyboard.">
+    Touch the fingerprint sensor with your finger
+  </message>
+  <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_KEYBOARD_BOTTOM_LEFT_ARIA_LABEL" desc="Aria label in the add fingerprint dialog telling users that the fingerprint sensor is in the bottom left corner of the keyboard. Only visible by screen reader software.">
+    The fingerprint sensor is the bottom left-hand key on your keyboard. Touch it lightly with any finger.
+  </message>
+    <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_KEYBOARD_BOTTOM_RIGHT_ARIA_LABEL" desc="Aria label in the add fingerprint dialog telling users that the fingerprint sensor is in the bottom right corner of the keyboard. Only visible by screen reader software.">
+    The fingerprint sensor is the bottom right-hand key on your keyboard. Touch it lightly with any finger.
+  </message>
+    <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_KEYBOARD_TOP_RIGHT_ARIA_LABEL" desc="Aria label in the add fingerprint dialog telling users that the fingerprint sensor is in the top right corner of the keyboard. Only visible by screen reader software.">
+    The fingerprint sensor is the top right-hand key on your keyboard. Touch it lightly with any finger.
+  </message>
 
   <!-- Languages and Inputs (OS settings) -->
   <message name="IDS_OS_SETTINGS_LANGUAGES_AND_INPUT_PAGE_TITLE" desc="Name of the OS settings page which displays language method preferences.">
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 07e77dac..2dc1f328 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -3,13 +3,6 @@
 <grit-part>
   <message name="IDS_SETTINGS_EMPTY_STRING" desc="Empty string, exist only to make code generic. No translation required."></message>
 
-  <!-- Main Page -->
-  <if expr="chromeos">
-    <message name="IDS_SETTINGS_SECONDARY_USER_BANNER" desc="Banner displayed in settings page when the user is secondary in a multi-profile session.">
-      Some settings belonging to <ph name="PRIMARY_EMAIL">$1<ex>john@google.com</ex></ph> are being shared with you. These settings only affect your account when using multiple sign-in.
-    </message>
-  </if>
-
   <!-- Shared across multiple pages -->
   <message name="IDS_SETTINGS_CONTINUE" desc="Label for 'Continue' buttons.">
     Continue
@@ -176,12 +169,6 @@
     </message>
   </if>
   <if expr="chromeos">
-    <message name="IDS_SETTINGS_DEVICE_OFF" desc="In Settings pages, the label when a bluetooth, wifi, or mobile device is off (disabled)." meaning="Device is disabled.">
-      Off
-    </message>
-    <message name="IDS_SETTINGS_DEVICE_ON" desc="In Settings pages, the label when a bluetooth, wifi, or mobile device is on (enabled)." meaning="Device is enabled.">
-      On
-    </message>
     <message name="IDS_SETTINGS_RESTART" desc="Text for a button that will restart ChromeOS.">
       Restart
     </message>
@@ -843,225 +830,6 @@
     Open certain file types automatically after downloading
   </message>
 
-  <!-- Strings specific to the Settings Internet Page -->
-  <if expr="chromeos">
-    <message name="IDS_SETTINGS_INTERNET_CONFIG" desc="Title for the network configuration dialog.">
-      Configure network
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_CONFIG_SHARE" desc="Settings > Internet > Network config: Label for setting allowing other device users to use the network configuration.">
-      Allow other users of this device to use this network
-    </message>
-    <message name="IDS_SETTINGS_HIDDEN_NETWORK_WARNING" desc="Settings > Internet > Network config: Text shown when auto connect to the WiFi network is enabled, warning about the dangers of auto-connecting to hidden networks.">
-      Automatically connecting to a hidden network allows others to see your device and some network settings, and is not recommended.
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_CONFIG_SAVE_CREDENTIALS" desc="Settings > Internet > Network config: Label for the setting to save identity and password.">
-      Save identity and password
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_CA_USE_DEFAULT" desc="Settings > Internet > Network config: Label for selecting the default Certificate Authority for an EAP network.">
-      Default
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_CA_DO_NOT_CHECK" desc="Settings > Internet > Network config: Label for selecting that no Certificate Authority should be used for an EAP network.">
-      Do not check
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_NO_USER_CERT" desc="Settings > Internet > Network config: Label for selecting that no User Certificate should be used when connected to an OpenVPN network.">
-      No user certificate
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_CERTIFICATE_NAME" desc="Settings > Internet > Network config: Formatting string for network certificate names.">
-      <ph name="ISSUED_BY">$1<ex>Google Inc</ex></ph> [<ph name="ISSUED_TO">$2<ex>John Doe</ex></ph>]
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_CERTIFICATE_NAME_HARDWARE_BACKED" desc="Settings > Internet > Network configuration: Formatting string for network certificate names that are hardware backed.">
-      <ph name="ISSUED_BY">$1<ex>Google Inc</ex></ph> [<ph name="ISSUED_TO">$2<ex>John Doe</ex></ph>] (hardware-backed)
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_CERTIFICATE_NONE_INSTALLED" desc="Settings > Internet > Network configuration: Label when no certificates are installed.">
-      None installed
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_REQUIRE_HARDWARE_BACKED" desc="In the network connect dialog, when creating a VPN or enterprise Wi-Fi connection, error message to display when a non hardware-backed user certificate is selected.">
-      User certificate must be hardware-backed.
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_ACCESS_POINT" desc="Settings > Internet > Network details: Access Point section label.">
-      Access Point
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_NAMESERVERS" desc="Settings > Network: Name servers label.">
-      Name servers
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_NAMESERVERS_AUTOMATIC" desc="Settings > Internet > Network details: Label for setting automatic name servers.">
-      Automatic name servers
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_NAMESERVERS_CUSTOM" desc="Settings > Internet > Network details: Label for setting custom name servers.">
-      Custom name servers
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_NAMESERVERS_GOOGLE" desc="Settings > Internet > Network details: Label for setting Google name servers.">
-      Google name servers
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_NAMESERVERS_INPUT_ACCESSIBILITY_LABEL" desc="Settings > Internet > Network details: Accessibility only label for custom nameserver input box.">
-      Custom nameserver <ph name="INPUT_INDEX">$1<ex>1</ex></ph>
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_WPAD" desc="Settings > Internet > Network details: Web Proxy Auto Discovery (WPAD) label.">
-      Web Proxy Auto Discovery URL:
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_WPAD_NONE" desc="Settings > Internet > Network details: Value for Web Proxy Auto Discovery when no URL is provided.">
-      Not provided
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_CHOOSE_MOBILE" desc="Settings > Internet > Network details: Label for control to choose a mobile data network.">
-      Mobile data network
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_CELLULAR_SCAN" desc="Settings > Internet > Network details: Label for button to start a Cellular scan.">
-      Scan
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_CELLULAR_SCAN_COMPLETED" desc="Settings > Internet > Network details: Status text when a scan has completed.">
-      Scan completed
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_CELLULAR_SCAN_CONNECTED_HELP" desc="Settings > Internet > Network details: Help message for scan button when network is connected.">
-      Disconnect to enable scanning
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_CELLULAR_SCANNING" desc="Settings > Internet > Network details: Status text when a scan is in progress.">
-      Scanning...
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_CELLULAR_NO_NETWORKS" desc="Settings > Internet > Network details: Dropdown text when no cellular networks were found after a scan.">
-      No networks
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_ENFORCED_POLICY" desc="Settings > Internet > Network details: Text to show when a network proxy is policy enforced.">
-      This proxy is enforced by your administrator
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_ALLOW_SHARED" desc="Settings > Internet > Network details: Text next to the checkbox for allowing proxy settings for shared networks.">
-      Allow proxies for shared networks
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_ALLOW_SHARED_ENABLE_WARNING_TITLE" desc="Settings > Internet > Network details: Title for warning dialog when changing the allow shared proxies setting is enabled.">
-      Allow proxies for shared networks
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_ALLOW_SHARED_DISABLE_WARNING_TITLE" desc="Settings > Internet > Network details: Title for warning dialog when changing the allow shared proxies setting is disabled.">
-      Disallow proxies for shared networks
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_ALLOW_SHARED_WARNING_MESSAGE" desc="Settings > Internet > Network details: Warning message when changing the allow shared proxies setting.">
-      Changing this setting will affect all shared networks
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_TYPE_DIRECT" desc="Radio used to tell it to just connect directly, not use a proxy.">
-      Direct Internet connection
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_TYPE_MANUAL" desc="Radio used to tell it to configure manually.">
-      Manual proxy configuration
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_TYPE_PAC" desc="Radio to select configuring from a URL.">
-      Automatic proxy configuration
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_TYPE_WPAD" desc="Label for the checkbox that controls whether to use an autoconfiguration URL.">
-      Web proxy autodiscovery
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_USE_SAME" desc="Settings > Internet > Network details: Label for checkbox indicating that a network proxy uses the same value for all protocols.">
-      Use the same proxy for all protocols
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_EXCEPTION_INPUT_ACCESSIBILITY_LABEL" desc="Settings > Internet > Accessibility only label for input field to add proxy exceptions.">
-      Host or domain to exclude
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_EXCEPTION_LIST" desc="Settings > Internet > Network details: Label for list of network proxy exceptions.">
-      Do not use the proxy settings for these hosts and domains:
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_EXCEPT_REMOVE_ACCESSIBILITY_LABEL" desc="Settings > Internet > Network details: Accessibility only label for button to remove proxy exception.">
-      Remove exception for <ph name="HOST_NAME">$1<ex>example.com</ex></ph>
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_ADD_EXCEPTION" desc="Settings > Internet > Network details: Label for button to add a network proxy exception.">
-      Add exception
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_CONNECTION_TYPE" desc="Settings > Internet > Network details: Label for proxy connection type dropdown.">
-      Connection type
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_CONNECTION_TYPE_DIALOG" desc="Label for proxy connection type dropdown in the network details dialog used in the login screen.">
-      Proxy connection type
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_AUTO_CONFIG" desc="Settings > Internet > Network details: Label for proxy autoconfiguration URL input.">
-      Autoconfiguration URL:
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_PROXY" desc="Settings > Internet > Network details: Label for all proxies.">
-      Proxy
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_HOST_INPUT_ACCESSIBILITY_LABEL" desc="Settings > Internet > Network details: Accessibility only label for proxy host input field.">
-      <ph name="INPUT_LABEL">$1<ex>HTTP Proxy</ex></ph> - Host
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_HTTP_PROXY" desc="Settings > Internet > Network details: Label for HTTP proxy.">
-      HTTP Proxy
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_SHTTP_PROXY" desc="Settings > Internet > Network details: Label for SHTTP proxy.">
-      Secure HTTP Proxy
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_FTP_PROXY" desc="Settings > Internet > Network details: Label for FTP proxy.">
-      FTP Proxy
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_SOCKS_HOST" desc="Settings > Internet > Network details: Label for SOCKS host proxy.">
-      SOCKS Host
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_PORT" desc="Settings > Internet > Network details: Label for proxy port input.">
-      Port
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_PORT_INPUT_ACCESSIBILITY_LABEL" desc="Settings > Internet > Network details: Accessibility only label for proxy port number input field.">
-      <ph name="INPUT_LABEL">$1<ex>HTTP Proxy</ex></ph> - Port
-    </message>
-
-    <!-- Settings > Internet > SIM lock/unlock dialog -->
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_CHANGE_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for buton to change the PIN.">
-      Change PIN
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_CARD_MISSING" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when SIM card is missing.">
-      Missing SIM card
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_CARD_LOCKED" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when SIM card is locked.">
-      SIM card is locked
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_LOCK_ENABLE" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for checkbox to enable SIM card locking.">
-      Enable SIM card locking (require PIN to use mobile data)
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_BUTTON_CHANGE" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for dialog button to change a PIN.">
-      Change
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_BUTTON_ENTER" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for dialog button to enter a PIN; note that in this context, 'Enter' refers to the act of entering a PIN, not the name of a key on the keyboard." meaning="Enter a PIN into the device. [Verb]">
-      Enter
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_BUTTON_UNLOCK" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for button to unlock the SIM card.">
-      Unlock
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ENTER_PIN_TITLE" desc="Settings > Internet > Network details > Lock/unlock SIM card: Title for enter PIN dialog.">
-      Enter SIM PIN
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_CHANGE_PIN_TITLE" desc="Settings > Internet > Network details > Lock/unlock SIM card: Title for change PIN dialog.">
-      Change SIM PIN
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_LOCKED_TITLE" desc="Settings > Internet > Network details > Lock/unlock SIM card: Title for SIM locked dialog.">
-      SIM Card is locked
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_LOCKED_WARNING" desc="Settings > Internet > Network details > Lock/unlock SIM card: Warning for SIM locked dialog when PUK is required.">
-      Your SIM card will be permanently disabled if you cannot enter the correct PIN Unlock Key.
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ENTER_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for entering a PIN.">
-      Enter PIN
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ENTER_OLD_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for entering an old (current) PIN.">
-      Enter old PIN
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ENTER_NEW_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for entering a new PIN.">
-      Enter new PIN
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_RE_ENTER_NEW_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for re-entering a new PIN.">
-      Re-enter new PIN
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ENTER_PUK" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for entering a PUK.">
-      Enter PIN Unlock Key
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ERROR_INCORRECT_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when an incorrect PIN was entered.">
-      Incorrect PIN. Retries left: <ph name="RETRIES">$1<ex>3</ex></ph>.
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ERROR_INCORRECT_PUK" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when an incorrect PUK was entered.">
-      Incorrect PUK. Retries left: <ph name="RETRIES">$1<ex>3</ex></ph>.
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ERROR_INVALID_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when an invalid PIN was entered.">
-      Invalid PIN. Retries left: <ph name="RETRIES">$1<ex>3</ex></ph>.
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ERROR_INVALID_PUK" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when an invalid PUK was entered.">
-      Invalid PUK. Retries left: <ph name="RETRIES">$1<ex>3</ex></ph>.
-    </message>
-    <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_ERROR_PIN_MISMATCH" desc="Settings > Internet > Network details > Lock/unlock SIM card: Message when PIN values do not match.">
-      PIN values do not match.
-    </message>
-  </if>
-
   <!-- On Startup Page -->
   <message name="IDS_SETTINGS_ON_STARTUP" desc="Name of the on startup page.">
     On startup
@@ -2494,50 +2262,6 @@
   <message name="IDS_SETTINGS_CHANGE_PICTURE_PROFILE_PHOTO" desc="The text on the Google profile photo of the user.">
     Google Profile photo
   </message>
-  <if expr="chromeos">
-    <message name="IDS_SETTINGS_PEOPLE_CONFIGURE_PIN_WEAK_PIN" desc="Message shown below the title that warns the user they have entered a PIN that is easy to guess.">
-      PIN may be easy to guess
-    </message>
-    <message name="IDS_SETTINGS_PEOPLE_CONFIGURE_PIN_TOO_SHORT" desc="Message shown below the title that tells the user that the PIN they entered needs to be at least minimum digits long.">
-      PIN must be at least <ph name="MINIMUM">$1<ex>4</ex></ph> digits
-    </message>
-    <message name="IDS_SETTINGS_PEOPLE_CONFIGURE_PIN_TOO_LONG" desc="Message shown below the title that tells the user that the PIN they entered needs to be less than maximum digits long.">
-      PIN must be less than <ph name="MAXIMUM">$1<ex>0</ex></ph> digits
-    </message>
-    <message name="IDS_SETTINGS_PEOPLE_CONFIGURE_PIN_MISMATCHED" desc="Message shown below the title that tells the user they have entered two different PIN values.">
-      PINs do not match
-    </message>
-    <message name="IDS_SETTINGS_ACCOUNT_MANAGER_ACCOUNT_REMOVED_MESSAGE" desc="Notification message after account removal.">
-      <ph name="EMAIL">$1<ex>abcd@google.com</ex></ph> was removed from this device
-    </message>
-    <message name="IDS_SETTINGS_ACCOUNT_MANAGER_EDUCATION_ACCOUNT" desc="Status label which indicates that specified account is a school/EDU account.">
-      School account
-    </message>
-    <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_POWER_BUTTON" desc="Text in the add fingerprint dialog telling users to place finger on the power button which is the sensor.">
-      Touch the power button with your finger
-    </message>
-     <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_POWER_BUTTON_ARIA_LABEL" desc="Aria label in the add fingerprint dialog telling users that the fingerprint sensor is in the power button. Only visible by screen reader software.">
-      The fingerprint sensor is in the power button. Touch it lightly with any finger.
-    </message>
-    <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_KEYBOARD" desc="Text in the add fingerprint dialog telling users to place finger on the sensor on the keyboard.">
-      Touch the fingerprint sensor with your finger
-    </message>
-    <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_KEYBOARD_BOTTOM_LEFT_ARIA_LABEL" desc="Aria label in the add fingerprint dialog telling users that the fingerprint sensor is in the bottom left corner of the keyboard. Only visible by screen reader software.">
-      The fingerprint sensor is the bottom left-hand key on your keyboard. Touch it lightly with any finger.
-    </message>
-     <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_KEYBOARD_BOTTOM_RIGHT_ARIA_LABEL" desc="Aria label in the add fingerprint dialog telling users that the fingerprint sensor is in the bottom right corner of the keyboard. Only visible by screen reader software.">
-      The fingerprint sensor is the bottom right-hand key on your keyboard. Touch it lightly with any finger.
-    </message>
-     <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_KEYBOARD_TOP_RIGHT_ARIA_LABEL" desc="Aria label in the add fingerprint dialog telling users that the fingerprint sensor is in the top right corner of the keyboard. Only visible by screen reader software.">
-      The fingerprint sensor is the top right-hand key on your keyboard. Touch it lightly with any finger.
-    </message>
-    <message name="IDS_SETTINGS_PEOPLE_PASSWORD_PROMPT_PASSWORD_LABEL" desc="An input box label that tells the user to enter their password in that input box.">
-      Password
-    </message>
-    <message name="IDS_SETTINGS_PEOPLE_PASSWORD_PROMPT_INVALID_PASSWORD" desc="Text on a password field that tells the user the password is incorrect.">
-      Invalid password
-    </message>
-  </if>
 
   <message name="IDS_SETTINGS_PEOPLE_SIGN_IN" desc="The label of the button that lets user sign-in to chrome.">
     Sign in
@@ -2585,19 +2309,6 @@
   <message name="IDS_SETTINGS_MANAGE_GOOGLE_ACCOUNT" desc="Label for the button that takes the user to the Google Account website.">
     Manage your Google Account
   </message>
-<if expr="chromeos">
-  <message name="IDS_SETTINGS_THEMES_AND_WALLPAPERS_CHECKBOX_LABEL" desc="Label for the checkbox which enables or disables syncing themes and wallpapers between multiple browser instances.">
-    Themes &amp; Wallpapers
-  </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
-  </message>
-</if>
-<if expr="not chromeos">
-  <message name="IDS_SETTINGS_THEMES_AND_WALLPAPERS_CHECKBOX_LABEL" desc="Label for the checkbox which enables or disables syncing themes and wallpapers between multiple browser instances.">
-    Themes
-  </message>
-</if>
   <message name="IDS_SETTINGS_USER_EVENTS_CHECKBOX_LABEL" desc="Label for the checkbox which enables or disables recording user events.">
     Activity and interactions
   </message>
diff --git a/chrome/app/shared_settings_strings.grdp b/chrome/app/shared_settings_strings.grdp
index c6127e1..35d4d3b4 100644
--- a/chrome/app/shared_settings_strings.grdp
+++ b/chrome/app/shared_settings_strings.grdp
@@ -250,4 +250,15 @@
   <message name="IDS_SETTINGS_ABOUT_UPGRADE_CHECK_STARTED" desc="Status label: About to start checking for updates">
     Checking for updates
   </message>
+
+  <if expr="chromeos">
+    <message name="IDS_SETTINGS_THEMES_AND_WALLPAPERS_CHECKBOX_LABEL" desc="Label for the checkbox which enables or disables syncing themes and wallpapers between multiple browser instances.">
+        Themes &amp; Wallpapers
+      </message>
+    </if>
+  <if expr="not chromeos">
+      <message name="IDS_SETTINGS_THEMES_AND_WALLPAPERS_CHECKBOX_LABEL" desc="Label for the checkbox which enables or disables syncing themes and wallpapers between multiple browser instances.">
+        Themes
+    </message>
+  </if>
 </grit-part>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 1afb2d9..3bee687 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1999,7 +1999,6 @@
     "//chrome/browser/metrics/variations:chrome_ui_string_overrider_factory",
     "//chrome/browser/net:probe_message_proto",
     "//chrome/browser/notifications",
-    "//chrome/browser/performance_manager:site_data_proto",
     "//chrome/browser/profiling_host",
     "//chrome/browser/push_messaging:budget_proto",
     "//chrome/browser/reputation:proto",
@@ -2381,6 +2380,8 @@
       "android/color_helpers.h",
       "android/component_updater/background_task_update_scheduler.cc",
       "android/component_updater/background_task_update_scheduler.h",
+      "android/compose_bitmaps_helper.cc",
+      "android/compose_bitmaps_helper.h",
       "android/compositor/compositor_view.cc",
       "android/compositor/compositor_view.h",
       "android/compositor/decoration_title.cc",
@@ -3386,33 +3387,10 @@
       "performance_manager/mechanisms/page_discarder.h",
       "performance_manager/mechanisms/page_loader.cc",
       "performance_manager/mechanisms/page_loader.h",
-      "performance_manager/persistence/site_data/exponential_moving_average.cc",
-      "performance_manager/persistence/site_data/exponential_moving_average.h",
-      "performance_manager/persistence/site_data/feature_usage.h",
-      "performance_manager/persistence/site_data/leveldb_site_data_store.cc",
-      "performance_manager/persistence/site_data/leveldb_site_data_store.h",
-      "performance_manager/persistence/site_data/non_recording_site_data_cache.cc",
-      "performance_manager/persistence/site_data/non_recording_site_data_cache.h",
-      "performance_manager/persistence/site_data/noop_site_data_writer.cc",
-      "performance_manager/persistence/site_data/noop_site_data_writer.h",
-      "performance_manager/persistence/site_data/site_data_cache.h",
       "performance_manager/persistence/site_data/site_data_cache_facade.cc",
       "performance_manager/persistence/site_data/site_data_cache_facade.h",
       "performance_manager/persistence/site_data/site_data_cache_facade_factory.cc",
       "performance_manager/persistence/site_data/site_data_cache_facade_factory.h",
-      "performance_manager/persistence/site_data/site_data_cache_factory.cc",
-      "performance_manager/persistence/site_data/site_data_cache_factory.h",
-      "performance_manager/persistence/site_data/site_data_cache_impl.cc",
-      "performance_manager/persistence/site_data/site_data_cache_impl.h",
-      "performance_manager/persistence/site_data/site_data_cache_inspector.h",
-      "performance_manager/persistence/site_data/site_data_impl.cc",
-      "performance_manager/persistence/site_data/site_data_impl.h",
-      "performance_manager/persistence/site_data/site_data_reader.cc",
-      "performance_manager/persistence/site_data/site_data_reader.h",
-      "performance_manager/persistence/site_data/site_data_store.h",
-      "performance_manager/persistence/site_data/site_data_writer.cc",
-      "performance_manager/persistence/site_data/site_data_writer.h",
-      "performance_manager/persistence/site_data/tab_visibility.h",
       "permissions/attestation_permission_request.cc",
       "permissions/attestation_permission_request.h",
       "policy/device_account_initializer.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 25c70fc..b4f803e 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3538,6 +3538,10 @@
      flag_descriptions::kStoragePressureUIDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kStoragePressureUI)},
 
+    {"installed-apps-in-cbd", flag_descriptions::kInstalledAppsInCbdName,
+     flag_descriptions::kInstalledAppsInCbdDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(features::kInstalledAppsInCbd)},
+
     {"enable-network-logging-to-file",
      flag_descriptions::kEnableNetworkLoggingToFileName,
      flag_descriptions::kEnableNetworkLoggingToFileDescription, kOsAll,
diff --git a/chrome/browser/android/bookmarks/bookmark_bridge.cc b/chrome/browser/android/bookmarks/bookmark_bridge.cc
index 787c2166..6dcc702 100644
--- a/chrome/browser/android/bookmarks/bookmark_bridge.cc
+++ b/chrome/browser/android/bookmarks/bookmark_bridge.cc
@@ -23,6 +23,7 @@
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/android/chrome_jni_headers/BookmarkBridge_jni.h"
+#include "chrome/browser/android/bookmarks/partner_bookmarks_reader.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/bookmarks/managed_bookmark_service_factory.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
@@ -59,7 +60,6 @@
 using bookmarks::android::JavaBookmarkIdGetType;
 using bookmarks::BookmarkModel;
 using bookmarks::BookmarkNode;
-using bookmarks::BookmarkPermanentNode;
 using bookmarks::BookmarkType;
 using content::BrowserThread;
 
@@ -199,7 +199,7 @@
   if (partner_bookmarks_shim_->IsLoaded())
       return;
   partner_bookmarks_shim_->SetPartnerBookmarksRoot(
-      std::make_unique<BookmarkPermanentNode>(0, BookmarkNode::FOLDER));
+      PartnerBookmarksReader::CreatePartnerBookmarksRootForTesting());
   PartnerBookmarksShim::DisablePartnerBookmarksEditing();
   DCHECK(partner_bookmarks_shim_->IsLoaded());
 }
@@ -211,8 +211,8 @@
     const JavaParamRef<jobject>& obj) {
   if (partner_bookmarks_shim_->IsLoaded())
     return;
-  std::unique_ptr<BookmarkPermanentNode> root_partner_node =
-      std::make_unique<BookmarkPermanentNode>(0, BookmarkNode::FOLDER);
+  std::unique_ptr<BookmarkNode> root_partner_node =
+      PartnerBookmarksReader::CreatePartnerBookmarksRootForTesting();
   BookmarkNode* partner_bookmark_a =
       root_partner_node->Add(std::make_unique<BookmarkNode>(
           1, base::GenerateGUID(), GURL("http://www.a.com")));
diff --git a/chrome/browser/android/bookmarks/partner_bookmarks_reader.cc b/chrome/browser/android/bookmarks/partner_bookmarks_reader.cc
index 3024aea..d54d909 100644
--- a/chrome/browser/android/bookmarks/partner_bookmarks_reader.cc
+++ b/chrome/browser/android/bookmarks/partner_bookmarks_reader.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/favicon/large_icon_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "components/bookmarks/browser/bookmark_model.h"
 #include "components/favicon/core/favicon_service.h"
 #include "components/favicon/core/large_icon_service_impl.h"
 #include "components/favicon_base/favicon_types.h"
@@ -37,7 +36,6 @@
 using base::android::JavaRef;
 using base::android::ScopedJavaGlobalRef;
 using bookmarks::BookmarkNode;
-using bookmarks::BookmarkPermanentNode;
 using content::BrowserThread;
 
 namespace {
@@ -110,6 +108,10 @@
   return nullptr;
 }
 
+std::unique_ptr<BookmarkNode> CreatePartnerBookmarksRoot(int id) {
+  return std::make_unique<BookmarkNode>(id, base::GenerateGUID(), GURL());
+}
+
 }  // namespace
 
 PartnerBookmarksReader::PartnerBookmarksReader(
@@ -206,9 +208,8 @@
     node_id = node->id();
     const_cast<BookmarkNode*>(parent)->Add(std::move(node));
   } else {
-    std::unique_ptr<BookmarkPermanentNode> node =
-        std::make_unique<BookmarkPermanentNode>(wip_next_available_id_++,
-                                                BookmarkNode::FOLDER);
+    std::unique_ptr<BookmarkNode> node =
+        CreatePartnerBookmarksRoot(wip_next_available_id_++);
     node_id = node->id();
     node->SetTitle(title);
     wip_partner_bookmarks_root_ = std::move(node);
@@ -216,6 +217,12 @@
   return node_id;
 }
 
+// static
+std::unique_ptr<BookmarkNode>
+PartnerBookmarksReader::CreatePartnerBookmarksRootForTesting() {
+  return CreatePartnerBookmarksRoot(/*id=*/0);
+}
+
 void PartnerBookmarksReader::GetFavicon(const GURL& page_url,
                                         Profile* profile,
                                         bool fallback_to_server,
diff --git a/chrome/browser/android/bookmarks/partner_bookmarks_reader.h b/chrome/browser/android/bookmarks/partner_bookmarks_reader.h
index 447d617..f0aa732 100644
--- a/chrome/browser/android/bookmarks/partner_bookmarks_reader.h
+++ b/chrome/browser/android/bookmarks/partner_bookmarks_reader.h
@@ -11,7 +11,7 @@
 
 #include "base/android/jni_weak_ref.h"
 #include "base/macros.h"
-#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_node.h"
 
 namespace favicon {
 class LargeIconService;
@@ -48,6 +48,9 @@
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj);
 
+  static std::unique_ptr<bookmarks::BookmarkNode>
+  CreatePartnerBookmarksRootForTesting();
+
  private:
   // These values are persisted to logs. Entries should not be renumbered and
   // numeric values should never be reused.
diff --git a/chrome/browser/android/bookmarks/partner_bookmarks_shim.cc b/chrome/browser/android/bookmarks/partner_bookmarks_shim.cc
index 7bedaa02..2971ce7 100644
--- a/chrome/browser/android/bookmarks/partner_bookmarks_shim.cc
+++ b/chrome/browser/android/bookmarks/partner_bookmarks_shim.cc
@@ -13,6 +13,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
+#include "chrome/browser/android/bookmarks/partner_bookmarks_reader.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
 #include "components/bookmarks/browser/bookmark_model.h"
diff --git a/chrome/browser/android/bookmarks/partner_bookmarks_shim.h b/chrome/browser/android/bookmarks/partner_bookmarks_shim.h
index d962a2e..6aa0040 100644
--- a/chrome/browser/android/bookmarks/partner_bookmarks_shim.h
+++ b/chrome/browser/android/bookmarks/partner_bookmarks_shim.h
@@ -16,7 +16,7 @@
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/supports_user_data.h"
-#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_node.h"
 #include "components/bookmarks/browser/bookmark_utils.h"
 #include "components/url_formatter/url_formatter.h"
 #include "net/base/escape.h"
diff --git a/chrome/browser/android/bookmarks/partner_bookmarks_shim_unittest.cc b/chrome/browser/android/bookmarks/partner_bookmarks_shim_unittest.cc
index 56040ec4..8e168b53 100644
--- a/chrome/browser/android/bookmarks/partner_bookmarks_shim_unittest.cc
+++ b/chrome/browser/android/bookmarks/partner_bookmarks_shim_unittest.cc
@@ -10,9 +10,8 @@
 #include "base/macros.h"
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/bookmarks/bookmark_model_factory.h"
+#include "chrome/browser/android/bookmarks/partner_bookmarks_reader.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/test/bookmark_test_helpers.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/test/browser_task_environment.h"
@@ -20,9 +19,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
-using bookmarks::BookmarkModel;
 using bookmarks::BookmarkNode;
-using bookmarks::BookmarkPermanentNode;
 using testing::_;
 
 class MockObserver : public PartnerBookmarksShim::Observer {
@@ -37,36 +34,17 @@
 
 class PartnerBookmarksShimTest : public testing::Test {
  public:
-  PartnerBookmarksShimTest() : model_(nullptr) {}
+  PartnerBookmarksShimTest() = default;
 
-  TestingProfile* profile() const { return profile_.get(); }
   PartnerBookmarksShim* partner_bookmarks_shim() const {
     return PartnerBookmarksShim::BuildForBrowserContext(profile_.get());
   }
 
-  const BookmarkNode* AddBookmark(const BookmarkNode* parent,
-                                  const GURL& url,
-                                  const base::string16& title) {
-    if (!parent)
-      parent = model_->bookmark_bar_node();
-    return model_->AddURL(parent, parent->children().size(), title, url);
-  }
-
-  const BookmarkNode* AddFolder(const BookmarkNode* parent,
-                                const base::string16& title) {
-    if (!parent)
-      parent = model_->bookmark_bar_node();
-    return model_->AddFolder(parent, parent->children().size(), title);
-  }
-
  protected:
   // testing::Test
   void SetUp() override {
     profile_.reset(new TestingProfile());
     profile_->CreateBookmarkModel(true);
-
-    model_ = BookmarkModelFactory::GetForBrowserContext(profile_.get());
-    bookmarks::test::WaitForBookmarkModelToLoad(model_);
   }
 
   void TearDown() override {
@@ -80,7 +58,6 @@
 
   content::BrowserTaskEnvironment task_environment_;
 
-  BookmarkModel* model_;
   MockObserver observer_;
 
  private:
@@ -88,9 +65,9 @@
 };
 
 TEST_F(PartnerBookmarksShimTest, GetNodeByID) {
-  std::unique_ptr<BookmarkPermanentNode> root_partner_node =
-      std::make_unique<BookmarkPermanentNode>(0, BookmarkNode::FOLDER);
-  BookmarkPermanentNode* root_partner_node_ptr = root_partner_node.get();
+  std::unique_ptr<BookmarkNode> root_partner_node =
+      PartnerBookmarksReader::CreatePartnerBookmarksRootForTesting();
+  BookmarkNode* root_partner_node_ptr = root_partner_node.get();
   BookmarkNode* partner_folder1 = root_partner_node->Add(
       std::make_unique<BookmarkNode>(1, base::GenerateGUID(), GURL()));
 
@@ -126,10 +103,10 @@
 
 TEST_F(PartnerBookmarksShimTest, ObserverNotifiedOfLoadWithPartnerBookmarks) {
   EXPECT_CALL(observer_, PartnerShimLoaded(_)).Times(0);
-  int64_t id = 5;
-  std::unique_ptr<BookmarkPermanentNode> root_partner_node =
-      std::make_unique<BookmarkPermanentNode>(id++, BookmarkNode::FOLDER);
+  std::unique_ptr<BookmarkNode> root_partner_node =
+      PartnerBookmarksReader::CreatePartnerBookmarksRootForTesting();
 
+  int64_t id = 5;
   root_partner_node->Add(std::make_unique<BookmarkNode>(
       id++, base::GenerateGUID(), GURL("http://www.a.com")));
 
@@ -147,9 +124,9 @@
   EXPECT_CALL(observer_, PartnerShimLoaded(shim)).Times(0);
   EXPECT_CALL(observer_, PartnerShimChanged(shim)).Times(0);
 
-  std::unique_ptr<BookmarkPermanentNode> root_partner_node =
-      std::make_unique<BookmarkPermanentNode>(0, BookmarkNode::FOLDER);
-  BookmarkPermanentNode* root_partner_node_ptr = root_partner_node.get();
+  std::unique_ptr<BookmarkNode> root_partner_node =
+      PartnerBookmarksReader::CreatePartnerBookmarksRootForTesting();
+  BookmarkNode* root_partner_node_ptr = root_partner_node.get();
   root_partner_node->SetTitle(base::ASCIIToUTF16("Partner bookmarks"));
 
   BookmarkNode* partner_folder1 = root_partner_node->Add(
@@ -238,9 +215,9 @@
   EXPECT_CALL(observer_, PartnerShimLoaded(shim)).Times(0);
   EXPECT_CALL(observer_, PartnerShimChanged(shim)).Times(0);
 
-  std::unique_ptr<BookmarkPermanentNode> root_partner_node =
-      std::make_unique<BookmarkPermanentNode>(0, BookmarkNode::FOLDER);
-  BookmarkPermanentNode* root_partner_node_ptr = root_partner_node.get();
+  std::unique_ptr<BookmarkNode> root_partner_node =
+      PartnerBookmarksReader::CreatePartnerBookmarksRootForTesting();
+  BookmarkNode* root_partner_node_ptr = root_partner_node.get();
   root_partner_node->SetTitle(base::ASCIIToUTF16("Partner bookmarks"));
 
   BookmarkNode* partner_folder1 = root_partner_node->Add(
@@ -322,8 +299,8 @@
     EXPECT_CALL(observer_, PartnerShimLoaded(shim)).Times(0);
     EXPECT_CALL(observer_, PartnerShimChanged(shim)).Times(0);
 
-    std::unique_ptr<BookmarkPermanentNode> root_partner_node =
-        std::make_unique<BookmarkPermanentNode>(0, BookmarkNode::FOLDER);
+    std::unique_ptr<BookmarkNode> root_partner_node =
+        PartnerBookmarksReader::CreatePartnerBookmarksRootForTesting();
     root_partner_node->SetTitle(base::ASCIIToUTF16("Partner bookmarks"));
 
     BookmarkNode* partner_folder1 = root_partner_node->Add(
@@ -377,8 +354,8 @@
   EXPECT_CALL(observer_, PartnerShimLoaded(shim)).Times(0);
   EXPECT_CALL(observer_, PartnerShimChanged(shim)).Times(0);
 
-  std::unique_ptr<BookmarkPermanentNode> root_partner_node =
-      std::make_unique<BookmarkPermanentNode>(0, BookmarkNode::FOLDER);
+  std::unique_ptr<BookmarkNode> root_partner_node =
+      PartnerBookmarksReader::CreatePartnerBookmarksRootForTesting();
   root_partner_node->SetTitle(base::ASCIIToUTF16("Partner bookmarks"));
 
   BookmarkNode* partner_bookmark1 =
@@ -410,8 +387,8 @@
 }
 
 TEST_F(PartnerBookmarksShimTest, GetPartnerBookmarksMatchingProperties) {
-  std::unique_ptr<BookmarkPermanentNode> root_partner_node =
-      std::make_unique<BookmarkPermanentNode>(0, BookmarkNode::FOLDER);
+  std::unique_ptr<BookmarkNode> root_partner_node =
+      PartnerBookmarksReader::CreatePartnerBookmarksRootForTesting();
   BookmarkNode* partner_folder1 = root_partner_node->Add(
       std::make_unique<BookmarkNode>(1, base::GenerateGUID(), GURL()));
   partner_folder1->SetTitle(base::ASCIIToUTF16("Folder1"));
diff --git a/chrome/browser/android/compose_bitmaps_helper.cc b/chrome/browser/android/compose_bitmaps_helper.cc
new file mode 100644
index 0000000..8905485f
--- /dev/null
+++ b/chrome/browser/android/compose_bitmaps_helper.cc
@@ -0,0 +1,139 @@
+// 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/android/compose_bitmaps_helper.h"
+
+#include "base/logging.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkFilterQuality.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
+#include "third_party/skia/include/core/SkPixmap.h"
+
+namespace compose_bitmaps_helper {
+
+// Ratio of icon size to the amount of padding between the icons.
+const int kIconPaddingScale = 8;
+
+std::unique_ptr<SkBitmap> ComposeBitmaps(const std::vector<SkBitmap>& bitmaps,
+                                         int desired_size_in_pixel) {
+  int num_icons = bitmaps.size();
+  DVLOG(1) << "num icons: " << num_icons;
+  if (num_icons == 0) {
+    return nullptr;
+  }
+
+  DVLOG(1) << "desired_size_in_pixel: " << desired_size_in_pixel;
+  int icon_padding_pixel_size = desired_size_in_pixel / kIconPaddingScale;
+
+  // Offset to write icons out of frame due to padding.
+  int icon_write_offset = icon_padding_pixel_size / 2;
+
+  SkBitmap composite_bitmap;
+  SkImageInfo image_info =
+      bitmaps[0]
+          .info()
+          .makeWH(desired_size_in_pixel, desired_size_in_pixel)
+          .makeAlphaType(kPremul_SkAlphaType);
+
+  composite_bitmap.setInfo(image_info);
+  composite_bitmap.allocPixels();
+
+  int icon_size = desired_size_in_pixel / 2;
+
+  // draw icons in correct areas
+  switch (num_icons) {
+    case 1: {
+      // Centered.
+      SkBitmap scaledBitmap = ScaleBitmap(icon_size, bitmaps[0]);
+      if (scaledBitmap.empty()) {
+        return nullptr;
+      }
+      composite_bitmap.writePixels(
+          scaledBitmap.pixmap(),
+          ((icon_size + icon_padding_pixel_size) / 2) - icon_write_offset,
+          ((icon_size + icon_padding_pixel_size) / 2) - icon_write_offset);
+      break;
+    }
+    case 2: {
+      // Side by side.
+      for (int i = 0; i < 2; i++) {
+        SkBitmap scaledBitmap = ScaleBitmap(icon_size, bitmaps[i]);
+        if (scaledBitmap.empty()) {
+          return nullptr;
+        }
+        composite_bitmap.writePixels(
+            scaledBitmap.pixmap(),
+            (i * (icon_size + icon_padding_pixel_size)) - icon_write_offset,
+            ((icon_size + icon_padding_pixel_size) / 2) - icon_write_offset);
+      }
+      break;
+    }
+    case 3: {
+      // Two on top, one on bottom.
+      for (int i = 0; i < 3; i++) {
+        SkBitmap scaledBitmap = ScaleBitmap(icon_size, bitmaps[i]);
+        if (scaledBitmap.empty()) {
+          return nullptr;
+        }
+        switch (i) {
+          case 0:
+            composite_bitmap.writePixels(
+                scaledBitmap.pixmap(), -icon_write_offset, -icon_write_offset);
+            break;
+          case 1:
+            composite_bitmap.writePixels(
+                scaledBitmap.pixmap(),
+                (icon_size + icon_padding_pixel_size) - icon_write_offset,
+                -icon_write_offset);
+            break;
+          default:
+            composite_bitmap.writePixels(
+                scaledBitmap.pixmap(),
+                ((icon_size + icon_padding_pixel_size) / 2) - icon_write_offset,
+                (icon_size + icon_padding_pixel_size) - icon_write_offset);
+            break;
+        }
+      }
+      break;
+    }
+    case 4: {
+      // One in each corner.
+      for (int i = 0; i < 2; i++) {
+        for (int j = 0; j < 2; j++) {
+          int index = i + 2 * j;
+          SkBitmap scaledBitmap = ScaleBitmap(icon_size, bitmaps[index]);
+          if (scaledBitmap.empty()) {
+            return nullptr;
+          }
+          composite_bitmap.writePixels(
+              scaledBitmap.pixmap(),
+              (j * (icon_size + icon_padding_pixel_size)) - icon_write_offset,
+              (i * (icon_size + icon_padding_pixel_size)) - icon_write_offset);
+        }
+      }
+      break;
+    }
+    default:
+      DLOG(ERROR) << "Invalid number of icons to combine: " << bitmaps.size();
+      return nullptr;
+  }
+
+  return std::make_unique<SkBitmap>(composite_bitmap);
+}
+
+SkBitmap ScaleBitmap(int icon_size, const SkBitmap& bitmap) {
+  SkBitmap temp_bitmap;
+  SkImageInfo scaledIconInfo = bitmap.info().makeWH(icon_size, icon_size);
+  temp_bitmap.setInfo(scaledIconInfo);
+  temp_bitmap.allocPixels();
+  bool did_scale =
+      bitmap.pixmap().scalePixels(temp_bitmap.pixmap(), kHigh_SkFilterQuality);
+  if (!did_scale) {
+    DLOG(ERROR) << "Unable to scale icon";
+    return SkBitmap();
+  }
+  return temp_bitmap;
+}
+
+}  // namespace compose_bitmaps_helper
diff --git a/chrome/browser/android/compose_bitmaps_helper.h b/chrome/browser/android/compose_bitmaps_helper.h
new file mode 100644
index 0000000..d924f46
--- /dev/null
+++ b/chrome/browser/android/compose_bitmaps_helper.h
@@ -0,0 +1,21 @@
+// 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_ANDROID_COMPOSE_BITMAPS_HELPER_H_
+#define CHROME_BROWSER_ANDROID_COMPOSE_BITMAPS_HELPER_H_
+
+#include <vector>
+
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace compose_bitmaps_helper {
+
+// This method composes a list of bitmaps, up to four, into one single bitmap.
+std::unique_ptr<SkBitmap> ComposeBitmaps(const std::vector<SkBitmap>& bitmaps,
+                                         int desired_size_in_pixel);
+SkBitmap ScaleBitmap(int icon_size, const SkBitmap& bitmap);
+
+}  // namespace compose_bitmaps_helper
+
+#endif  // CHROME_BROWSER_ANDROID_COMPOSE_BITMAPS_HELPER_H_
diff --git a/chrome/browser/android/explore_sites/image_helper.cc b/chrome/browser/android/explore_sites/image_helper.cc
index e83e16a2..d59e027 100644
--- a/chrome/browser/android/explore_sites/image_helper.cc
+++ b/chrome/browser/android/explore_sites/image_helper.cc
@@ -8,6 +8,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/time/time.h"
+#include "chrome/browser/android/compose_bitmaps_helper.h"
 #include "chrome/browser/android/explore_sites/explore_sites_types.h"
 #include "services/data_decoder/public/cpp/decode_image.h"
 #include "third_party/skia/include/core/SkBitmap.h"
@@ -18,14 +19,6 @@
 #include "ui/gfx/geometry/size.h"
 
 namespace explore_sites {
-
-namespace {
-
-// Ratio of icon size to the amount of padding between the icons.
-const int kIconPaddingScale = 8;
-
-}  // namespace
-
 // Class Job is used to manage multiple calls to the ImageHelper. Each request
 // to the ImageHelper is handled by a single Job, which is then destroyed after
 // it is finished.
@@ -146,123 +139,8 @@
   }
 }
 
-SkBitmap ScaleBitmap(int icon_size, SkBitmap* bitmap) {
-  DCHECK(bitmap);
-  SkBitmap temp_bitmap;
-  SkImageInfo scaledIconInfo = bitmap->info().makeWH(icon_size, icon_size);
-  temp_bitmap.setInfo(scaledIconInfo);
-  temp_bitmap.allocPixels();
-  bool did_scale =
-      bitmap->pixmap().scalePixels(temp_bitmap.pixmap(), kHigh_SkFilterQuality);
-  if (!did_scale) {
-    DLOG(ERROR) << "Unable to scale icon for category image.";
-    return SkBitmap();
-  }
-  return temp_bitmap;
-}
-
 std::unique_ptr<SkBitmap> ImageHelper::Job::CombineImages() {
-  DVLOG(1) << "num icons: " << num_icons_;
-  if (num_icons_ == 0) {
-    return nullptr;
-  }
-
-  DVLOG(1) << "pixel_size_: " << pixel_size_;
-  int icon_padding_pixel_size = pixel_size_ / kIconPaddingScale;
-
-  // Offset to write icons out of frame due to padding.
-  int icon_write_offset = icon_padding_pixel_size / 2;
-
-  SkBitmap composite_bitmap;
-  SkImageInfo image_info = bitmaps_[0]
-                               .info()
-                               .makeWH(pixel_size_, pixel_size_)
-                               .makeAlphaType(kPremul_SkAlphaType);
-
-  composite_bitmap.setInfo(image_info);
-  composite_bitmap.allocPixels();
-
-  int icon_size = pixel_size_ / 2;
-
-  // draw icons in correct areas
-  switch (num_icons_) {
-    case 1: {
-      // Centered.
-      SkBitmap scaledBitmap = ScaleBitmap(icon_size, &bitmaps_[0]);
-      if (scaledBitmap.empty()) {
-        return nullptr;
-      }
-      composite_bitmap.writePixels(
-          scaledBitmap.pixmap(),
-          ((icon_size + icon_padding_pixel_size) / 2) - icon_write_offset,
-          ((icon_size + icon_padding_pixel_size) / 2) - icon_write_offset);
-      break;
-    }
-    case 2: {
-      // Side by side.
-      for (int i = 0; i < 2; i++) {
-        SkBitmap scaledBitmap = ScaleBitmap(icon_size, &bitmaps_[i]);
-        if (scaledBitmap.empty()) {
-          return nullptr;
-        }
-        composite_bitmap.writePixels(
-            scaledBitmap.pixmap(),
-            (i * (icon_size + icon_padding_pixel_size)) - icon_write_offset,
-            ((icon_size + icon_padding_pixel_size) / 2) - icon_write_offset);
-      }
-      break;
-    }
-    case 3: {
-      // Two on top, one on bottom.
-      for (int i = 0; i < 3; i++) {
-        SkBitmap scaledBitmap = ScaleBitmap(icon_size, &bitmaps_[i]);
-        if (scaledBitmap.empty()) {
-          return nullptr;
-        }
-        switch (i) {
-          case 0:
-            composite_bitmap.writePixels(
-                scaledBitmap.pixmap(), -icon_write_offset, -icon_write_offset);
-            break;
-          case 1:
-            composite_bitmap.writePixels(
-                scaledBitmap.pixmap(),
-                (icon_size + icon_padding_pixel_size) - icon_write_offset,
-                -icon_write_offset);
-            break;
-          default:
-            composite_bitmap.writePixels(
-                scaledBitmap.pixmap(),
-                ((icon_size + icon_padding_pixel_size) / 2) - icon_write_offset,
-                (icon_size + icon_padding_pixel_size) - icon_write_offset);
-            break;
-        }
-      }
-      break;
-    }
-    case 4: {
-      // One in each corner.
-      for (int i = 0; i < 2; i++) {
-        for (int j = 0; j < 2; j++) {
-          int index = i + 2 * j;
-          SkBitmap scaledBitmap = ScaleBitmap(icon_size, &bitmaps_[index]);
-          if (scaledBitmap.empty()) {
-            return nullptr;
-          }
-          composite_bitmap.writePixels(
-              scaledBitmap.pixmap(),
-              (j * (icon_size + icon_padding_pixel_size)) - icon_write_offset,
-              (i * (icon_size + icon_padding_pixel_size)) - icon_write_offset);
-        }
-      }
-      break;
-    }
-    default:
-      DLOG(ERROR) << "Invalid number of icons to combine: " << bitmaps_.size();
-      return nullptr;
-  }
-
-  return std::make_unique<SkBitmap>(composite_bitmap);
+  return compose_bitmaps_helper::ComposeBitmaps(bitmaps_, pixel_size_);
 }
 
 ImageHelper::ImageHelper() : last_used_job_id_(0) {}
diff --git a/chrome/browser/android/favicon_helper.cc b/chrome/browser/android/favicon_helper.cc
index 29dfac4..5b82cec 100644
--- a/chrome/browser/android/favicon_helper.cc
+++ b/chrome/browser/android/favicon_helper.cc
@@ -16,6 +16,7 @@
 #include "base/bind.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/android/compose_bitmaps_helper.h"
 #include "chrome/browser/favicon/favicon_service_factory.h"
 #include "chrome/browser/favicon/history_ui_favicon_request_handler_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -184,10 +185,13 @@
   if (!favicon_service)
     return false;
 
+  int desired_size_in_pixel = static_cast<int>(j_desired_size_in_pixel);
+
   favicon_base::FaviconResultsCallback callback_runner =
       base::BindOnce(&FaviconHelper::OnFaviconBitmapResultsAvailable,
                      weak_ptr_factory_.GetWeakPtr(),
-                     ScopedJavaGlobalRef<jobject>(j_favicon_image_callback));
+                     ScopedJavaGlobalRef<jobject>(j_favicon_image_callback),
+                     desired_size_in_pixel);
 
   std::vector<std::string> urls;
   base::android::AppendJavaStringArrayToStringVector(env, j_urls, &urls);
@@ -462,12 +466,32 @@
 
 void FaviconHelper::OnFaviconBitmapResultsAvailable(
     const JavaRef<jobject>& j_favicon_image_callback,
+    const int desired_size_in_pixel,
     const std::vector<favicon_base::FaviconRawBitmapResult>& results) {
-  JNIEnv* env = AttachCurrentThread();
-  // TODO(crbug.com/1064153): Integrate with ImageHelper to compose favicons.
-  // Convert favicon_image_result to java objects.
-  ScopedJavaLocalRef<jstring> j_icon_url;
+  std::vector<SkBitmap> result_bitmaps;
+
+  for (size_t i = 0; i < results.size(); i++) {
+    favicon_base::FaviconRawBitmapResult result = results[i];
+    if (!result.is_valid())
+      continue;
+    SkBitmap favicon_bitmap;
+    gfx::PNGCodec::Decode(result.bitmap_data->front(),
+                          result.bitmap_data->size(), &favicon_bitmap);
+    result_bitmaps.push_back(std::move(favicon_bitmap));
+  }
+
   ScopedJavaLocalRef<jobject> j_favicon_bitmap;
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jstring> j_icon_url;
+
+  if (!result_bitmaps.empty()) {
+    std::unique_ptr<SkBitmap> composed_bitmap =
+        compose_bitmaps_helper::ComposeBitmaps(std::move(result_bitmaps),
+                                               desired_size_in_pixel);
+    if (composed_bitmap && !composed_bitmap->isNull()) {
+      j_favicon_bitmap = gfx::ConvertToJavaBitmap(composed_bitmap.get());
+    }
+  }
 
   // Call java side OnFaviconBitmapResultAvailable method.
   Java_FaviconImageCallback_onFaviconAvailable(env, j_favicon_image_callback,
diff --git a/chrome/browser/android/favicon_helper.h b/chrome/browser/android/favicon_helper.h
index 25e30c9..40281f1 100644
--- a/chrome/browser/android/favicon_helper.h
+++ b/chrome/browser/android/favicon_helper.h
@@ -111,6 +111,7 @@
 
   void OnFaviconBitmapResultsAvailable(
       const base::android::JavaRef<jobject>& j_favicon_image_callback,
+      const int desired_size_in_pixel,
       const std::vector<favicon_base::FaviconRawBitmapResult>& result);
 
   std::unique_ptr<base::CancelableTaskTracker> cancelable_task_tracker_;
diff --git a/chrome/browser/apps/app_service/menu_util.cc b/chrome/browser/apps/app_service/menu_util.cc
index 88b1923..335aac8 100644
--- a/chrome/browser/apps/app_service/menu_util.cc
+++ b/chrome/browser/apps/app_service/menu_util.cc
@@ -13,6 +13,7 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/vector_icon_types.h"
 
@@ -173,7 +174,7 @@
       const gfx::VectorIcon& icon =
           std::move(get_vector_icon).Run(item->command_id, item->string_id);
       model->AddItemWithStringIdAndIcon(item->command_id, item->string_id,
-                                        icon);
+                                        ui::ImageModel::FromVectorIcon(icon));
       break;
     }
     case apps::mojom::MenuItemType::kSubmenu:
@@ -182,7 +183,8 @@
         const gfx::VectorIcon& icon =
             std::move(get_vector_icon).Run(item->command_id, item->string_id);
         model->AddActionableSubmenuWithStringIdAndIcon(
-            item->command_id, item->string_id, submenu, icon);
+            item->command_id, item->string_id, submenu,
+            ui::ImageModel::FromVectorIcon(icon));
       }
       break;
     case apps::mojom::MenuItemType::kRadio:
@@ -204,7 +206,7 @@
       break;
     case apps::mojom::MenuItemType::kArcCommand: {
       model->AddItemWithIcon(item->command_id, base::UTF8ToUTF16(item->label),
-                             item->image);
+                             ui::ImageModel::FromImageSkia(item->image));
       arc::ArcAppShortcutItem arc_shortcut_item;
       arc_shortcut_item.shortcut_id = item->shortcut_id;
       arc_shortcut_items->push_back(arc_shortcut_item);
diff --git a/chrome/browser/background/background_mode_manager.cc b/chrome/browser/background/background_mode_manager.cc
index 4527e7a..118837bf 100644
--- a/chrome/browser/background/background_mode_manager.cc
+++ b/chrome/browser/background/background_mode_manager.cc
@@ -68,6 +68,7 @@
 #include "extensions/common/manifest_handlers/options_page_info.h"
 #include "extensions/common/permissions/permission_set.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/image/image_family.h"
 
@@ -157,7 +158,8 @@
           base::RetainedRef(application)));
       menu->AddItem(command_id, base::UTF8ToUTF16(name));
       if (!icon.isNull())
-        menu->SetIcon(menu->GetItemCount() - 1, gfx::Image(icon));
+        menu->SetIcon(menu->GetItemCount() - 1,
+                      ui::ImageModel::FromImageSkia(icon));
 
       // Component extensions with background that do not have an options page
       // will cause this menu item to go to the extensions page with an
diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc
index 1973f26..c43b8568 100644
--- a/chrome/browser/browser_process_impl.cc
+++ b/chrome/browser/browser_process_impl.cc
@@ -1195,6 +1195,8 @@
   CreateNetworkQualityObserver();
 
 #if defined(OS_ANDROID)
+  // This needs to be here so that SecurityStateClient is non-null when
+  // SecurityStateModel code is called.
   security_state::SetSecurityStateClient(new ChromeSecurityStateClient());
 #endif
 }
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 59ea4c8..e163f2f 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -385,9 +385,8 @@
       service_manager::BinderRegistry* registry,
       blink::AssociatedInterfaceRegistry* associated_registry,
       content::RenderProcessHost* render_process_host) override;
-  void ExposeInterfacesToMediaService(
-      service_manager::BinderRegistry* registry,
-      content::RenderFrameHost* render_frame_host) override;
+  void BindMediaServiceReceiver(content::RenderFrameHost* render_frame_host,
+                                mojo::GenericPendingReceiver receiver) override;
   void RegisterBrowserInterfaceBindersForFrame(
       content::RenderFrameHost* render_frame_host,
       service_manager::BinderMapWithContext<content::RenderFrameHost*>* map)
diff --git a/chrome/browser/chrome_content_browser_client_receiver_bindings.cc b/chrome/browser/chrome_content_browser_client_receiver_bindings.cc
index b99d35e..9fc37a2 100644
--- a/chrome/browser/chrome_content_browser_client_receiver_bindings.cc
+++ b/chrome/browser/chrome_content_browser_client_receiver_bindings.cc
@@ -181,18 +181,26 @@
   }
 }
 
-void ChromeContentBrowserClient::ExposeInterfacesToMediaService(
-    service_manager::BinderRegistry* registry,
-    content::RenderFrameHost* render_frame_host) {
+void ChromeContentBrowserClient::BindMediaServiceReceiver(
+    content::RenderFrameHost* render_frame_host,
+    mojo::GenericPendingReceiver receiver) {
 #if BUILDFLAG(ENABLE_LIBRARY_CDMS)
-  registry->AddInterface(
-      base::Bind(&OutputProtectionImpl::Create, render_frame_host));
-  registry->AddInterface(
-      base::Bind(&PlatformVerificationImpl::Create, render_frame_host));
+  if (auto r = receiver.As<media::mojom::OutputProtection>()) {
+    OutputProtectionImpl::Create(render_frame_host, std::move(r));
+    return;
+  }
+
+  if (auto r = receiver.As<media::mojom::PlatformVerification>()) {
+    PlatformVerificationImpl::Create(render_frame_host, std::move(r));
+    return;
+  }
 #endif  // BUILDFLAG(ENABLE_LIBRARY_CDMS)
 
 #if BUILDFLAG(ENABLE_MOJO_CDM) && defined(OS_ANDROID)
-  registry->AddInterface(base::Bind(&CreateMediaDrmStorage, render_frame_host));
+  if (auto r = receiver.As<media::mojom::MediaDrmStorage>()) {
+    CreateMediaDrmStorage(render_frame_host, std::move(r));
+    return;
+  }
 #endif
 }
 
diff --git a/chrome/browser/chromeos/arc/app_shortcuts/arc_app_shortcuts_menu_builder.cc b/chrome/browser/chromeos/arc/app_shortcuts/arc_app_shortcuts_menu_builder.cc
index 120a4c3..46dbc55 100644
--- a/chrome/browser/chromeos/arc/app_shortcuts/arc_app_shortcuts_menu_builder.cc
+++ b/chrome/browser/chromeos/arc/app_shortcuts/arc_app_shortcuts_menu_builder.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/ui/app_list/search/search_controller.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_data.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/models/simple_menu_model.h"
 
 namespace arc {
@@ -107,8 +108,9 @@
     for (const auto& item : items) {
       if (command_id != command_id_first_)
         menu_model->AddSeparator(ui::PADDED_SEPARATOR);
-      menu_model->AddItemWithIcon(
-          command_id++, base::UTF8ToUTF16(item.short_label), item.icon);
+      menu_model->AddItemWithIcon(command_id++,
+                                  base::UTF8ToUTF16(item.short_label),
+                                  ui::ImageModel::FromImageSkia(item.icon));
     }
   }
   std::move(callback).Run(std::move(menu_model));
diff --git a/chrome/browser/chromeos/browser_context_keyed_service_factories.cc b/chrome/browser/chromeos/browser_context_keyed_service_factories.cc
index 45f6542..cb7b650 100644
--- a/chrome/browser/chromeos/browser_context_keyed_service_factories.cc
+++ b/chrome/browser/chromeos/browser_context_keyed_service_factories.cc
@@ -7,14 +7,18 @@
 #include "chrome/browser/chromeos/account_manager/account_manager_migrator.h"
 #include "chrome/browser/chromeos/android_sms/android_sms_service_factory.h"
 #include "chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.h"
+#include "chrome/browser/chromeos/authpolicy/authpolicy_credentials_manager.h"
 #include "chrome/browser/chromeos/bluetooth/debug_logs_manager_factory.h"
 #include "chrome/browser/chromeos/extensions/file_manager/event_router_factory.h"
 #include "chrome/browser/chromeos/extensions/input_method_api.h"
 #include "chrome/browser/chromeos/extensions/login_screen/login_state/session_state_changed_event_dispatcher.h"
 #include "chrome/browser/chromeos/extensions/media_player_api.h"
 #include "chrome/browser/chromeos/extensions/printing_metrics/print_job_finished_event_dispatcher.h"
+#include "chrome/browser/chromeos/file_manager/volume_manager_factory.h"
+#include "chrome/browser/chromeos/file_system_provider/service_factory.h"
 #include "chrome/browser/chromeos/guest_os/guest_os_registry_service_factory.h"
 #include "chrome/browser/chromeos/kerberos/kerberos_credentials_manager_factory.h"
+#include "chrome/browser/chromeos/launcher_search_provider/launcher_search_provider_service_factory.h"
 #include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_factory.h"
 #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_factory.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_engagement_metrics_service.h"
@@ -39,6 +43,7 @@
   AccountManagerMigratorFactory::GetInstance();
   android_sms::AndroidSmsServiceFactory::GetInstance();
   arc::ArcAccessibilityHelperBridge::CreateFactory();
+  AuthPolicyCredentialsManagerFactory::GetInstance();
   bluetooth::DebugLogsManagerFactory::GetInstance();
 #if defined(USE_CUPS)
   CupsProxyServiceManagerFactory::GetInstance();
@@ -54,8 +59,11 @@
   extensions::PrintJobFinishedEventDispatcher::GetFactoryInstance();
   extensions::SessionStateChangedEventDispatcher::GetFactoryInstance();
   file_manager::EventRouterFactory::GetInstance();
+  file_manager::VolumeManagerFactory::GetInstance();
+  file_system_provider::ServiceFactory::GetInstance();
   guest_os::GuestOsRegistryServiceFactory::GetInstance();
   KerberosCredentialsManagerFactory::GetInstance();
+  launcher_search_provider::ServiceFactory::GetInstance();
   OwnerSettingsServiceChromeOSFactory::GetInstance();
   plugin_vm::PluginVmEngagementMetricsService::Factory::GetInstance();
   policy::PolicyCertServiceFactory::GetInstance();
diff --git a/chrome/browser/chromeos/drive/OWNERS b/chrome/browser/chromeos/drive/OWNERS
index 931afe6..a165f77 100644
--- a/chrome/browser/chromeos/drive/OWNERS
+++ b/chrome/browser/chromeos/drive/OWNERS
@@ -1,3 +1,5 @@
+austinct@chromium.org
+dats@chromium.org
 hashimoto@chromium.org
 hidehiko@chromium.org
 hirono@chromium.org
diff --git a/chrome/browser/chromeos/drive/drive_integration_service.cc b/chrome/browser/chromeos/drive/drive_integration_service.cc
index f61333c..9aa5227 100644
--- a/chrome/browser/chromeos/drive/drive_integration_service.cc
+++ b/chrome/browser/chromeos/drive/drive_integration_service.cc
@@ -169,10 +169,6 @@
   return FILE_ERROR_OK;
 }
 
-void ResetCacheDone(base::OnceCallback<void(bool)> callback, FileError error) {
-  std::move(callback).Run(error == FILE_ERROR_OK);
-}
-
 base::FilePath GetFullPath(internal::ResourceMetadataStorage* metadata_storage,
                            const ResourceEntry& entry) {
   std::vector<std::string> path_components;
@@ -689,17 +685,55 @@
 }
 
 void DriveIntegrationService::ClearCacheAndRemountFileSystem(
-    const base::Callback<void(bool)>& callback) {
+    base::OnceCallback<void(bool)> callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK(callback);
-
-  if (state_ != INITIALIZED || !GetDriveFsInterface()) {
-    callback.Run(false);
+  if (in_clear_cache_) {
+    std::move(callback).Run(false);
     return;
   }
+  in_clear_cache_ = true;
 
-  GetDriveFsInterface()->ResetCache(mojo::WrapCallbackWithDefaultInvokeIfNotRun(
-      base::BindOnce(&ResetCacheDone, callback), FILE_ERROR_ABORT));
+  if (IsMounted()) {
+    RemoveDriveMountPoint();
+    // TODO(crbug/1069328): We wait 2 seconds here so that DriveFS can unmount
+    // completely. Ideally we'd wait for an unmount complete callback.
+    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&DriveIntegrationService::
+                           ClearCacheAndRemountFileSystemAfterUnmount,
+                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
+        base::TimeDelta::FromSeconds(2));
+  } else {
+    ClearCacheAndRemountFileSystemAfterUnmount(std::move(callback));
+  }
+}
+
+void DriveIntegrationService::ClearCacheAndRemountFileSystemAfterUnmount(
+    base::OnceCallback<void(bool)> callback) {
+  bool success = true;
+  base::FilePath cache_path = GetDriveFsHost()->GetDataPath();
+  base::FilePath logs_path = GetDriveFsLogPath().DirName();
+  base::FileEnumerator content_enumerator(
+      cache_path, false,
+      base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES |
+          base::FileEnumerator::SHOW_SYM_LINKS);
+  for (base::FilePath path = content_enumerator.Next(); !path.empty();
+       path = content_enumerator.Next()) {
+    // Keep the logs folder as it's useful for debugging.
+    if (path == logs_path) {
+      continue;
+    }
+    if (!base::DeleteFileRecursively(path)) {
+      success = false;
+      break;
+    }
+  }
+
+  if (is_enabled()) {
+    AddDriveMountPoint();
+  }
+  in_clear_cache_ = false;
+  std::move(callback).Run(success);
 }
 
 drivefs::DriveFsHost* DriveIntegrationService::GetDriveFsHost() const {
diff --git a/chrome/browser/chromeos/drive/drive_integration_service.h b/chrome/browser/chromeos/drive/drive_integration_service.h
index d7ee6e5..dd431cd 100644
--- a/chrome/browser/chromeos/drive/drive_integration_service.h
+++ b/chrome/browser/chromeos/drive/drive_integration_service.h
@@ -152,12 +152,10 @@
 
   EventLogger* event_logger() { return logger_.get(); }
 
-  // Clears all the local cache file, the local resource metadata, and
-  // in-memory Drive app registry, and remounts the file system. |callback|
+  // Clears all the local cache folder and remounts the file system. |callback|
   // is called with true when this operation is done successfully. Otherwise,
   // |callback| is called with false. |callback| must not be null.
-  void ClearCacheAndRemountFileSystem(
-      const base::Callback<void(bool)>& callback);
+  void ClearCacheAndRemountFileSystem(base::OnceCallback<void(bool)> callback);
 
   // Returns the DriveFsHost if it is enabled.
   drivefs::DriveFsHost* GetDriveFsHost() const;
@@ -208,6 +206,11 @@
   void MaybeRemountFileSystem(base::Optional<base::TimeDelta> remount_delay,
                               bool failed_to_mount);
 
+  // Helper function for ClearCacheAndRemountFileSystem() that deletes the cache
+  // folder and remounts Drive.
+  void ClearCacheAndRemountFileSystemAfterUnmount(
+      base::OnceCallback<void(bool)> callback);
+
   // Initializes the object. This function should be called before any
   // other functions.
   void Initialize();
@@ -243,6 +246,7 @@
   State state_;
   bool enabled_;
   bool mount_failed_ = false;
+  bool in_clear_cache_ = false;
   // Custom mount point name that can be injected for testing in constructor.
   std::string mount_point_name_;
 
diff --git a/chrome/browser/chromeos/drive/drive_integration_service_browsertest.cc b/chrome/browser/chromeos/drive/drive_integration_service_browsertest.cc
index ddf98e3e..702564dd 100644
--- a/chrome/browser/chromeos/drive/drive_integration_service_browsertest.cc
+++ b/chrome/browser/chromeos/drive/drive_integration_service_browsertest.cc
@@ -4,7 +4,11 @@
 
 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
 
+#include "base/bind.h"
 #include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/test/bind_test_util.h"
+#include "base/threading/thread_restrictions.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -24,6 +28,34 @@
       browser()->profile()));
 }
 
+IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTest,
+                       ClearCacheAndRemountFileSystem) {
+  base::ScopedAllowBlockingForTesting allow_blocking;
+
+  auto* drive_service =
+      DriveIntegrationServiceFactory::FindForProfile(browser()->profile());
+  base::FilePath cache_path = drive_service->GetDriveFsHost()->GetDataPath();
+  base::FilePath log_folder_path = drive_service->GetDriveFsLogPath().DirName();
+  ASSERT_TRUE(base::CreateDirectory(cache_path));
+  ASSERT_TRUE(base::CreateDirectory(log_folder_path));
+
+  base::FilePath cache_file;
+  base::FilePath log_file;
+  ASSERT_TRUE(base::CreateTemporaryFileInDir(cache_path, &cache_file));
+  ASSERT_TRUE(base::CreateTemporaryFileInDir(log_folder_path, &log_file));
+
+  base::RunLoop run_loop;
+  auto quit_closure = run_loop.QuitClosure();
+  drive_service->ClearCacheAndRemountFileSystem(
+      base::BindLambdaForTesting([=](bool success) {
+        EXPECT_TRUE(success);
+        EXPECT_FALSE(base::PathExists(cache_file));
+        EXPECT_TRUE(base::PathExists(log_file));
+        quit_closure.Run();
+      }));
+
+  run_loop.Run();
+}
 
 IN_PROC_BROWSER_TEST_F(DriveIntegrationServiceBrowserTest,
                        DisableDrivePolicyTest) {
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
index 9b54f8b..2385cdb 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
@@ -498,6 +498,7 @@
         TestCase("openQuickViewKeyboardUpDownChangesView"),
         TestCase("openQuickViewKeyboardLeftRightChangesView"),
         TestCase("openQuickViewSniffedText"),
+        TestCase("openQuickViewTextFileWithUnknownMimeType"),
         TestCase("openQuickViewScrollText"),
         TestCase("openQuickViewScrollHtml"),
         TestCase("openQuickViewMhtml"),
diff --git a/chrome/browser/chromeos/file_manager/file_manager_jstest.cc b/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
index bb8b130..e63f9f52 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
@@ -27,6 +27,10 @@
   RunTestURL("foreground/js/actions_model_unittest_gen.html");
 }
 
+IN_PROC_BROWSER_TEST_F(FileManagerJsTest, Breadcrumb) {
+  RunTestURL("foreground/js/ui/breadcrumb_unittest_gen.html");
+}
+
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, NavigationListModelTest) {
   RunTestURL("foreground/js/navigation_list_model_unittest_gen.html");
 }
diff --git a/chrome/browser/chromeos/input_method/OWNERS b/chrome/browser/chromeos/input_method/OWNERS
index e275c13..f084c17 100644
--- a/chrome/browser/chromeos/input_method/OWNERS
+++ b/chrome/browser/chromeos/input_method/OWNERS
@@ -1,7 +1,8 @@
 # primary reviewers
+keithlee@chromium.org
+shend@chromium.org
 shuchen@chromium.org
 yhanada@chromium.org
-keithlee@chromium.org
 
 # backup reviewers
 yukishiino@chromium.org
diff --git a/chrome/browser/chromeos/login/ui/captive_portal_window_proxy.cc b/chrome/browser/chromeos/login/ui/captive_portal_window_proxy.cc
index a232ec11..9bb18a9c 100644
--- a/chrome/browser/chromeos/login/ui/captive_portal_window_proxy.cc
+++ b/chrome/browser/chromeos/login/ui/captive_portal_window_proxy.cc
@@ -6,7 +6,7 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "chrome/browser/chromeos/login/ui/captive_portal_view.h"
-#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/ui/webui/chromeos/internet_detail_dialog.h"
 #include "components/constrained_window/constrained_window_views.h"
 #include "components/web_modal/web_contents_modal_dialog_host.h"
@@ -16,11 +16,37 @@
 
 namespace {
 
+// A widget that uses the supplied Profile to return a ThemeProvider.  This is
+// necessary because the Views in the captive portal UI need to access the theme
+// colors; also, this widget cannot copy the theme from e.g. a Browser widget
+// because there may be no Browsers started.
+class CaptivePortalWidget : public views::Widget {
+ public:
+  explicit CaptivePortalWidget(Profile* profile);
+  CaptivePortalWidget(const CaptivePortalWidget&) = delete;
+  CaptivePortalWidget& operator=(const CaptivePortalWidget&) = delete;
+  ~CaptivePortalWidget() override = default;
+
+  // views::Widget:
+  const ui::ThemeProvider* GetThemeProvider() const override;
+
+ private:
+  Profile* profile_;
+};
+
+CaptivePortalWidget::CaptivePortalWidget(Profile* profile)
+    : profile_(profile) {}
+
+const ui::ThemeProvider* CaptivePortalWidget::GetThemeProvider() const {
+  return &ThemeService::GetThemeProviderForProfile(profile_);
+}
+
 // The captive portal dialog is system-modal, but uses the web-content-modal
 // dialog manager (odd) and requires this atypical dialog widget initialization.
-views::Widget* CreateWindowAsFramelessChild(views::WidgetDelegate* delegate,
+views::Widget* CreateWindowAsFramelessChild(Profile* profile,
+                                            views::WidgetDelegate* delegate,
                                             gfx::NativeView parent) {
-  views::Widget* widget = new views::Widget;
+  views::Widget* widget = new CaptivePortalWidget(profile);
 
   views::Widget::InitParams params;
   params.delegate = delegate;
@@ -39,17 +65,14 @@
 CaptivePortalWindowProxy::CaptivePortalWindowProxy(
     Delegate* delegate,
     content::WebContents* web_contents)
-    : delegate_(delegate),
-      widget_(NULL),
-      web_contents_(web_contents),
-      captive_portal_view_for_testing_(NULL) {
-  DCHECK(GetState() == STATE_IDLE);
+    : delegate_(delegate), web_contents_(web_contents) {
+  DCHECK_EQ(STATE_IDLE, GetState());
 }
 
 CaptivePortalWindowProxy::~CaptivePortalWindowProxy() {
   if (!widget_)
     return;
-  DCHECK(GetState() == STATE_DISPLAYED);
+  DCHECK_EQ(STATE_DISPLAYED, GetState());
   widget_->RemoveObserver(this);
   widget_->Close();
 }
@@ -58,7 +81,7 @@
   if (GetState() != STATE_IDLE)
     return;
   InitCaptivePortalView();
-  DCHECK(GetState() == STATE_WAITING_FOR_REDIRECTION);
+  DCHECK_EQ(STATE_WAITING_FOR_REDIRECTION, GetState());
 }
 
 void CaptivePortalWindowProxy::Show() {
@@ -80,7 +103,7 @@
   auto* manager =
       web_modal::WebContentsModalDialogManager::FromWebContents(web_contents_);
   widget_ = CreateWindowAsFramelessChild(
-      portal,
+      profile_, portal,
       manager->delegate()->GetWebContentsModalDialogHost()->GetHostView());
   portal->Init();
   widget_->AddObserver(this);
@@ -91,7 +114,7 @@
   if (GetState() == STATE_DISPLAYED)
     widget_->Close();
   captive_portal_view_.reset();
-  captive_portal_view_for_testing_ = NULL;
+  captive_portal_view_for_testing_ = nullptr;
 }
 
 void CaptivePortalWindowProxy::OnRedirected() {
@@ -119,12 +142,12 @@
 }
 
 void CaptivePortalWindowProxy::OnWidgetDestroyed(views::Widget* widget) {
-  DCHECK(GetState() == STATE_DISPLAYED);
-  DCHECK(widget == widget_);
+  DCHECK_EQ(STATE_DISPLAYED, GetState());
+  DCHECK_EQ(widget, widget_);
 
   DetachFromWidget(widget);
 
-  DCHECK(GetState() == STATE_IDLE);
+  DCHECK_EQ(STATE_IDLE, GetState());
 
   for (auto& observer : observers_)
     observer.OnAfterCaptivePortalHidden();
@@ -134,8 +157,7 @@
   DCHECK(GetState() == STATE_IDLE ||
          GetState() == STATE_WAITING_FOR_REDIRECTION);
   if (!captive_portal_view_.get()) {
-    captive_portal_view_.reset(
-        new CaptivePortalView(ProfileHelper::GetSigninProfile(), this));
+    captive_portal_view_ = std::make_unique<CaptivePortalView>(profile_, this);
     captive_portal_view_for_testing_ = captive_portal_view_.get();
   }
 
@@ -144,25 +166,17 @@
 }
 
 CaptivePortalWindowProxy::State CaptivePortalWindowProxy::GetState() const {
-  if (widget_ == NULL) {
-    if (captive_portal_view_.get() == NULL)
-      return STATE_IDLE;
-    else
-      return STATE_WAITING_FOR_REDIRECTION;
-  } else {
-    if (captive_portal_view_.get() == NULL)
-      return STATE_DISPLAYED;
-    else
-      NOTREACHED();
-  }
-  return STATE_UNKNOWN;
+  if (!widget_)
+    return captive_portal_view_ ? STATE_WAITING_FOR_REDIRECTION : STATE_IDLE;
+  DCHECK(!captive_portal_view_);
+  return STATE_DISPLAYED;
 }
 
 void CaptivePortalWindowProxy::DetachFromWidget(views::Widget* widget) {
   if (!widget_ || widget_ != widget)
     return;
   widget_->RemoveObserver(this);
-  widget_ = NULL;
+  widget_ = nullptr;
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/ui/captive_portal_window_proxy.h b/chrome/browser/chromeos/login/ui/captive_portal_window_proxy.h
index b538d7af..10e8d494 100644
--- a/chrome/browser/chromeos/login/ui/captive_portal_window_proxy.h
+++ b/chrome/browser/chromeos/login/ui/captive_portal_window_proxy.h
@@ -7,11 +7,10 @@
 
 #include <memory>
 
-#include "base/compiler_specific.h"
-#include "base/macros.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
 #include "base/time/time.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "ui/views/widget/widget_observer.h"
 
 namespace content {
@@ -33,7 +32,7 @@
   virtual void OnPortalDetected() = 0;
 
  protected:
-  virtual ~CaptivePortalWindowProxyDelegate() {}
+  virtual ~CaptivePortalWindowProxyDelegate() = default;
 };
 
 // Proxy which manages showing of the window for CaptivePortal sign-in.
@@ -44,18 +43,19 @@
   class Observer : public base::CheckedObserver {
    public:
     Observer() = default;
+    Observer(const Observer&) = delete;
+    Observer& operator=(const Observer&) = delete;
     ~Observer() override = default;
     virtual void OnBeforeCaptivePortalShown() {}
     virtual void OnAfterCaptivePortalHidden() {}
-
-   private:
-    DISALLOW_COPY_AND_ASSIGN(Observer);
   };
 
-  typedef CaptivePortalWindowProxyDelegate Delegate;
+  using Delegate = CaptivePortalWindowProxyDelegate;
 
   CaptivePortalWindowProxy(Delegate* delegate,
                            content::WebContents* web_contents);
+  CaptivePortalWindowProxy(const CaptivePortalWindowProxy&) = delete;
+  CaptivePortalWindowProxy& operator=(const CaptivePortalWindowProxy&) = delete;
   ~CaptivePortalWindowProxy() override;
 
   // Shows captive portal window only after a redirection has happened. So it is
@@ -121,22 +121,16 @@
     return captive_portal_view_for_testing_;
   }
 
-  // Not owned by this class.
+  Profile* profile_ = ProfileHelper::GetSigninProfile();
   Delegate* delegate_;
-  // Not owned by this class.
-  views::Widget* widget_;
-  std::unique_ptr<CaptivePortalView> captive_portal_view_;
-
-  // Not owned by this class.
   content::WebContents* web_contents_;
+  views::Widget* widget_ = nullptr;
 
-  CaptivePortalView* captive_portal_view_for_testing_;
+  std::unique_ptr<CaptivePortalView> captive_portal_view_;
+  CaptivePortalView* captive_portal_view_for_testing_ = nullptr;
 
   base::Time started_loading_at_;
-
   base::ObserverList<Observer> observers_;
-
-  DISALLOW_COPY_AND_ASSIGN(CaptivePortalWindowProxy);
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
index 6c85c3cf..8d09bce2 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
@@ -51,6 +51,7 @@
 #include "chrome/browser/chromeos/policy/enrollment_config.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/chromeos/system/device_disabling_manager.h"
 #include "chrome/browser/chromeos/system/input_device_settings.h"
 #include "chrome/browser/chromeos/system/timezone_resolver_manager.h"
 #include "chrome/browser/chromeos/system/timezone_util.h"
@@ -62,6 +63,7 @@
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
 #include "chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h"
+#include "chrome/browser/ui/webui/chromeos/login/device_disabled_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
 #include "chrome/browser/ui/webui/chromeos/login/welcome_screen_handler.h"
@@ -178,6 +180,20 @@
          first_screen == chromeos::OobeScreen::SCREEN_SPECIAL_LOGIN;
 }
 
+void MaybeShowDeviceDisabledScreen() {
+  DCHECK(chromeos::LoginDisplayHost::default_host());
+  if (!g_browser_process->platform_part()->device_disabling_manager()) {
+    // Device disabled check will be done in the DeviceDisablingManager.
+    return;
+  }
+
+  if (!system::DeviceDisablingManager::IsDeviceDisabledDuringNormalOperation())
+    return;
+
+  chromeos::LoginDisplayHost::default_host()->StartWizard(
+      DeviceDisabledScreenView::kScreenId);
+}
+
 // ShowLoginWizard is split into two parts. This function is sometimes called
 // from TriggerShowLoginWizardFinish() directly, and sometimes from
 // OnLanguageSwitchedCallback()
@@ -232,6 +248,7 @@
   DCHECK(session_manager::SessionManager::Get());
   DCHECK(chromeos::LoginDisplayHost::default_host());
   WallpaperControllerClient::Get()->SetInitialWallpaper();
+  MaybeShowDeviceDisabledScreen();
 }
 
 struct ShowLoginWizardSwitchLanguageCallbackData {
diff --git a/chrome/browser/chromeos/policy/auto_enrollment_client_impl.cc b/chrome/browser/chromeos/policy/auto_enrollment_client_impl.cc
index 565ccb58..caa2676 100644
--- a/chrome/browser/chromeos/policy/auto_enrollment_client_impl.cc
+++ b/chrome/browser/chromeos/policy/auto_enrollment_client_impl.cc
@@ -267,7 +267,7 @@
     parsed_response.is_license_packaged_with_device.reset();
 
     // Logging as "WARNING" to make sure it's preserved in the logs.
-    LOG(WARNING) << "Received restore_mode=" << state_response.restore_mode();
+    LOG(WARNING) << "Received restore_mode=" << parsed_response.restore_mode;
 
     return parsed_response;
   }
diff --git a/chrome/browser/chromeos/policy/device_display_cros_browser_test.cc b/chrome/browser/chromeos/policy/device_display_cros_browser_test.cc
new file mode 100644
index 0000000..28b6ae4
--- /dev/null
+++ b/chrome/browser/chromeos/policy/device_display_cros_browser_test.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 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/chromeos/policy/device_display_cros_browser_test.h"
+
+#include "ash/display/display_configuration_controller.h"
+#include "ash/shell.h"
+#include "chrome/browser/chromeos/login/ui/login_display_host.h"
+#include "chrome/browser/chromeos/policy/device_policy_builder.h"
+#include "chrome/browser/lifetime/application_lifetime.h"
+#include "chrome/test/base/mixin_based_in_process_browser_test.h"
+#include "chromeos/dbus/session_manager/session_manager_client.h"
+#include "components/policy/proto/chrome_device_policy.pb.h"
+#include "ui/display/display.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+gfx::Size DeviceDisplayCrosTestHelper::GetResolutionOfDisplay(
+    int64_t display_id) const {
+  display::ManagedDisplayMode display_mode;
+  if (GetDisplayManager()->GetSelectedModeForDisplayId(display_id,
+                                                       &display_mode)) {
+    return display_mode.size();
+  }
+  const display::Display& display =
+      GetDisplayManager()->GetDisplayForId(display_id);
+  return display.GetSizeInPixel();
+}
+
+int DeviceDisplayCrosTestHelper::GetScaleOfDisplay(int64_t display_id) const {
+  // Converting scale to percents.
+  display::ManagedDisplayMode display_mode;
+  const display::Display& display =
+      GetDisplayManager()->GetDisplayForId(display_id);
+  return floor(display.device_scale_factor() * 100.0 + 0.5);
+}
+
+display::DisplayManager* DeviceDisplayCrosTestHelper::GetDisplayManager()
+    const {
+  return ash::Shell::Get()->display_manager();
+}
+
+int64_t DeviceDisplayCrosTestHelper::GetFirstDisplayId() const {
+  return GetDisplayManager()->first_display_id();
+}
+
+int64_t DeviceDisplayCrosTestHelper::GetSecondDisplayId() const {
+  if (GetDisplayManager()->GetNumDisplays() < 2) {
+    ADD_FAILURE() << "The second display is not connected.";
+    return 0;
+  }
+  return GetDisplayManager()->GetCurrentDisplayIdList()[1];
+}
+
+const display::Display& DeviceDisplayCrosTestHelper::GetFirstDisplay() const {
+  return GetDisplayManager()->GetDisplayForId(GetFirstDisplayId());
+}
+
+const display::Display& DeviceDisplayCrosTestHelper::GetSecondDisplay() const {
+  return GetDisplayManager()->GetDisplayForId(GetSecondDisplayId());
+}
+
+display::Display::Rotation
+DeviceDisplayCrosTestHelper::GetRotationOfFirstDisplay() const {
+  return GetFirstDisplay().rotation();
+}
+
+// Fails the test and returns ROTATE_0 if there is no second display.
+display::Display::Rotation
+DeviceDisplayCrosTestHelper::GetRotationOfSecondDisplay() const {
+  return GetSecondDisplay().rotation();
+}
+
+double DeviceDisplayCrosTestHelper::GetScaleOfFirstDisplay() const {
+  return GetScaleOfDisplay(GetFirstDisplayId());
+}
+
+double DeviceDisplayCrosTestHelper::GetScaleOfSecondDisplay() const {
+  return GetScaleOfDisplay(GetSecondDisplayId());
+}
+
+gfx::Size DeviceDisplayCrosTestHelper::GetResolutionOfFirstDisplay() const {
+  return GetResolutionOfDisplay(GetFirstDisplayId());
+}
+
+gfx::Size DeviceDisplayCrosTestHelper::GetResolutionOfSecondDisplay() const {
+  return GetResolutionOfDisplay(GetSecondDisplayId());
+}
+
+void DeviceDisplayCrosTestHelper::ToggleSecondDisplay() {
+  GetDisplayManager()->AddRemoveDisplay();
+  base::RunLoop().RunUntilIdle();
+}
+
+void DeviceDisplayPolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture() {
+  chromeos::SessionManagerClient::InitializeFakeInMemory();
+  ash::DisplayConfigurationController::DisableAnimatorForTest();
+  DevicePolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture();
+}
+
+void DeviceDisplayPolicyCrosBrowserTest::TearDownOnMainThread() {
+  // If the login display is still showing, exit gracefully.
+  if (chromeos::LoginDisplayHost::default_host()) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(&chrome::AttemptExit));
+    RunUntilBrowserProcessQuits();
+  }
+  DevicePolicyCrosBrowserTest::TearDownOnMainThread();
+}
+
+}  // namespace policy
diff --git a/chrome/browser/chromeos/policy/device_display_cros_browser_test.h b/chrome/browser/chromeos/policy/device_display_cros_browser_test.h
new file mode 100644
index 0000000..434cd44
--- /dev/null
+++ b/chrome/browser/chromeos/policy/device_display_cros_browser_test.h
@@ -0,0 +1,66 @@
+// Copyright (c) 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_CHROMEOS_POLICY_DEVICE_DISPLAY_CROS_BROWSER_TEST_H_
+#define CHROME_BROWSER_CHROMEOS_POLICY_DEVICE_DISPLAY_CROS_BROWSER_TEST_H_
+
+#include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
+#include "ui/display/display.h"
+#include "ui/display/manager/display_manager.h"
+
+namespace policy {
+
+// A helper class to be used in the different tests that deal with policy driven
+// display setting changes.
+class DeviceDisplayCrosTestHelper {
+ public:
+  DeviceDisplayCrosTestHelper() = default;
+  DeviceDisplayCrosTestHelper(const DeviceDisplayCrosTestHelper&) = delete;
+  DeviceDisplayCrosTestHelper& operator=(const DeviceDisplayCrosTestHelper&) =
+      delete;
+  ~DeviceDisplayCrosTestHelper() = default;
+
+  display::DisplayManager* GetDisplayManager() const;
+  int64_t GetFirstDisplayId() const;
+  int64_t GetSecondDisplayId() const;
+  const display::Display& GetFirstDisplay() const;
+  const display::Display& GetSecondDisplay() const;
+  display::Display::Rotation GetRotationOfFirstDisplay() const;
+  display::Display::Rotation GetRotationOfSecondDisplay() const;
+  double GetScaleOfFirstDisplay() const;
+  double GetScaleOfSecondDisplay() const;
+  gfx::Size GetResolutionOfFirstDisplay() const;
+  gfx::Size GetResolutionOfSecondDisplay() const;
+  // Creates second display if there is none yet, or removes it if there is one.
+  void ToggleSecondDisplay();
+
+ protected:
+  gfx::Size GetResolutionOfDisplay(int64_t display_id) const;
+  int GetScaleOfDisplay(int64_t display_id) const;
+};
+
+class DeviceDisplayPolicyCrosBrowserTest
+    : public policy::DevicePolicyCrosBrowserTest {
+ public:
+  DeviceDisplayPolicyCrosBrowserTest() = default;
+  DeviceDisplayPolicyCrosBrowserTest(
+      const DeviceDisplayPolicyCrosBrowserTest&) = delete;
+  DeviceDisplayPolicyCrosBrowserTest& operator=(
+      const DeviceDisplayPolicyCrosBrowserTest&) = delete;
+  ~DeviceDisplayPolicyCrosBrowserTest() override = default;
+
+  void SetUpInProcessBrowserTestFixture() override;
+  void TearDownOnMainThread() override;
+
+  DeviceDisplayCrosTestHelper* display_helper() { return &display_helper_; }
+
+  void UnsetPolicy();
+
+ private:
+  DeviceDisplayCrosTestHelper display_helper_;
+};
+
+}  // namespace policy
+
+#endif
diff --git a/chrome/browser/chromeos/policy/device_policy_cros_browser_test.cc b/chrome/browser/chromeos/policy/device_policy_cros_browser_test.cc
index 16ced183..1195323 100644
--- a/chrome/browser/chromeos/policy/device_policy_cros_browser_test.cc
+++ b/chrome/browser/chromeos/policy/device_policy_cros_browser_test.cc
@@ -14,11 +14,13 @@
 #include "base/path_service.h"
 #include "base/threading/thread_restrictions.h"
 #include "chrome/browser/chromeos/policy/device_policy_builder.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/common/chrome_paths.h"
 #include "chromeos/constants/chromeos_paths.h"
 #include "chromeos/dbus/constants/dbus_paths.h"
 #include "chromeos/dbus/cryptohome/tpm_util.h"
 #include "chromeos/dbus/session_manager/fake_session_manager_client.h"
+#include "chromeos/settings/cros_settings_names.h"
 #include "chromeos/tpm/install_attributes.h"
 #include "crypto/rsa_private_key.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -28,6 +30,8 @@
 using ::testing::AnyNumber;
 using ::testing::Return;
 
+namespace em = enterprise_management;
+
 namespace policy {
 
 DevicePolicyCrosTestHelper::DevicePolicyCrosTestHelper() {}
@@ -58,17 +62,52 @@
   chromeos::RegisterStubPathOverrides(user_data_dir);
 }
 
-DevicePolicyCrosBrowserTest::DevicePolicyCrosBrowserTest()
-    : fake_session_manager_client_(new chromeos::FakeSessionManagerClient) {}
-
-DevicePolicyCrosBrowserTest::~DevicePolicyCrosBrowserTest() = default;
-
-void DevicePolicyCrosBrowserTest::RefreshDevicePolicy() {
+const std::string DevicePolicyCrosTestHelper::device_policy_blob() {
   // Reset the key to its original state.
   device_policy()->SetDefaultSigningKey();
   device_policy()->Build();
-  session_manager_client()->set_device_policy(device_policy()->GetBlob());
-  session_manager_client()->OnPropertyChangeComplete(true);
+  return device_policy()->GetBlob();
+}
+
+void DevicePolicyCrosTestHelper::RefreshDevicePolicy() {
+  chromeos::FakeSessionManagerClient::Get()->set_device_policy(
+      device_policy_blob());
+  chromeos::FakeSessionManagerClient::Get()->OnPropertyChangeComplete(true);
+}
+
+void DevicePolicyCrosTestHelper::RefreshPolicyAndWaitUntilDeviceSettingsUpdated(
+    const std::vector<std::string>& settings) {
+  base::RunLoop run_loop;
+
+  // For calls from SetPolicy().
+  std::vector<std::unique_ptr<chromeos::CrosSettings::ObserverSubscription>>
+      observers = {};
+  for (auto setting_it = settings.cbegin(); setting_it != settings.cend();
+       setting_it++) {
+    observers.push_back(chromeos::CrosSettings::Get()->AddSettingsObserver(
+        *setting_it, run_loop.QuitClosure()));
+  }
+  RefreshDevicePolicy();
+  run_loop.Run();
+  // Allow tasks posted by CrosSettings observers to complete:
+  base::RunLoop().RunUntilIdle();
+}
+
+void DevicePolicyCrosTestHelper::UnsetPolicy(
+    const std::vector<std::string>& settings) {
+  em::ChromeDeviceSettingsProto& proto(device_policy()->payload());
+  proto.clear_display_rotation_default();
+  proto.clear_device_display_resolution();
+  RefreshPolicyAndWaitUntilDeviceSettingsUpdated(settings);
+}
+
+DevicePolicyCrosBrowserTest::DevicePolicyCrosBrowserTest() {}
+
+DevicePolicyCrosBrowserTest::~DevicePolicyCrosBrowserTest() = default;
+
+chromeos::FakeSessionManagerClient*
+DevicePolicyCrosBrowserTest::session_manager_client() {
+  return chromeos::FakeSessionManagerClient::Get();
 }
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/device_policy_cros_browser_test.h b/chrome/browser/chromeos/policy/device_policy_cros_browser_test.h
index 118b180..7cb8128 100644
--- a/chrome/browser/chromeos/policy/device_policy_cros_browser_test.h
+++ b/chrome/browser/chromeos/policy/device_policy_cros_browser_test.h
@@ -23,54 +23,65 @@
 class DevicePolicyCrosTestHelper {
  public:
   DevicePolicyCrosTestHelper();
+  DevicePolicyCrosTestHelper(const DevicePolicyCrosTestHelper&) = delete;
+  DevicePolicyCrosTestHelper& operator=(const DevicePolicyCrosTestHelper&) =
+      delete;
   ~DevicePolicyCrosTestHelper();
 
+  DevicePolicyBuilder* device_policy() { return &device_policy_; }
+  const std::string device_policy_blob();
+
   // Writes the owner key to disk. To be called before installing a policy.
   void InstallOwnerKey();
 
-  DevicePolicyBuilder* device_policy() { return &device_policy_; }
+  // Reinstalls |device_policy_| as the policy (to be used when it was
+  // recently changed).
+  void RefreshDevicePolicy();
+  // Refreshes the Device Settings policies given in the settings vector.
+  // Example: {chromeos::kDeviceDisplayResolution} refreshes the display
+  //   resolution setting.
+  void RefreshPolicyAndWaitUntilDeviceSettingsUpdated(
+      const std::vector<std::string>& settings);
+  void UnsetPolicy(const std::vector<std::string>& settings);
 
  private:
   static void OverridePaths();
 
   // Carries Chrome OS device policies for tests.
   DevicePolicyBuilder device_policy_;
-
-  DISALLOW_COPY_AND_ASSIGN(DevicePolicyCrosTestHelper);
 };
 
 // Used to test Device policy changes in Chrome OS.
 class DevicePolicyCrosBrowserTest : public MixinBasedInProcessBrowserTest {
  protected:
   DevicePolicyCrosBrowserTest();
+  DevicePolicyCrosBrowserTest(const DevicePolicyCrosBrowserTest&) = delete;
+  DevicePolicyCrosBrowserTest& operator=(const DevicePolicyCrosBrowserTest&) =
+      delete;
   ~DevicePolicyCrosBrowserTest() override;
 
-  // Reinstalls |device_policy_| as the policy (to be used when it was
-  // recently changed).
-  void RefreshDevicePolicy();
-
+  void RefreshDevicePolicy() { policy_helper()->RefreshDevicePolicy(); }
   chromeos::DBusThreadManagerSetter* dbus_setter() {
     return dbus_setter_.get();
   }
 
-  chromeos::FakeSessionManagerClient* session_manager_client() {
-    return fake_session_manager_client_;
+  DevicePolicyBuilder* device_policy() {
+    return policy_helper()->device_policy();
   }
 
-  DevicePolicyBuilder* device_policy() { return test_helper_.device_policy(); }
+  chromeos::FakeSessionManagerClient* session_manager_client();
 
   chromeos::DeviceStateMixin device_state_{
       &mixin_host_,
       chromeos::DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED};
 
+  DevicePolicyCrosTestHelper* policy_helper() { return &policy_helper_; }
+
  private:
-  DevicePolicyCrosTestHelper test_helper_;
+  DevicePolicyCrosTestHelper policy_helper_;
 
   // FakeDBusThreadManager uses FakeSessionManagerClient.
   std::unique_ptr<chromeos::DBusThreadManagerSetter> dbus_setter_;
-  chromeos::FakeSessionManagerClient* fake_session_manager_client_;
-
-  DISALLOW_COPY_AND_ASSIGN(DevicePolicyCrosBrowserTest);
 };
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/display_resolution_handler_browsertest.cc b/chrome/browser/chromeos/policy/display_resolution_handler_browsertest.cc
index 6a5ca1e..0d92f158 100644
--- a/chrome/browser/chromeos/policy/display_resolution_handler_browsertest.cc
+++ b/chrome/browser/chromeos/policy/display_resolution_handler_browsertest.cc
@@ -15,8 +15,8 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/chromeos/login/test/device_state_mixin.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
+#include "chrome/browser/chromeos/policy/device_display_cros_browser_test.h"
 #include "chrome/browser/chromeos/policy/device_policy_builder.h"
-#include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/test/base/mixin_based_in_process_browser_test.h"
@@ -58,64 +58,9 @@
 };
 
 const gfx::Size kDefaultDisplayResolution(1280, 800);
-const gfx::Size kDefaultExternalDisplayResolution(1920, 1080);
+const gfx::Size kDefaultSecondDisplayResolution(1920, 1080);
 const int kDefaultDisplayScale = 100;
 
-display::DisplayManager* GetDisplayManager() {
-  return ash::Shell::Get()->display_manager();
-}
-
-int64_t GetInternalDisplayId() {
-  const display::DisplayManager* const display_manager = GetDisplayManager();
-  return display_manager->first_display_id();
-}
-
-gfx::Size GetResolution(int64_t display_id) {
-  const display::DisplayManager* const display_manager = GetDisplayManager();
-  display::ManagedDisplayMode display_mode;
-  if (display_manager->GetSelectedModeForDisplayId(display_id, &display_mode)) {
-    return display_mode.size();
-  }
-  const display::Display& display =
-      display_manager->GetDisplayForId(display_id);
-  return display.GetSizeInPixel();
-}
-
-double GetScale(int64_t display_id) {
-  const display::DisplayManager* const display_manager = GetDisplayManager();
-  display::ManagedDisplayMode display_mode;
-  const display::Display& display =
-      display_manager->GetDisplayForId(display_id);
-  return display.device_scale_factor();
-}
-
-int GetScaleOfInternalDisplay() {
-  // Converting scale to percents.
-  return floor(GetScale(GetInternalDisplayId()) * 100.0 + 0.5);
-}
-
-gfx::Size GetResolutionOfInternalDisplay() {
-  return GetResolution(GetInternalDisplayId());
-}
-
-int64_t GetExternalDisplayId() {
-  const display::DisplayManager* const display_manager = GetDisplayManager();
-  if (display_manager->GetNumDisplays() < 2) {
-    ADD_FAILURE() << "The external display is not connected.";
-    return 0;
-  }
-  return display_manager->GetCurrentDisplayIdList()[1];
-}
-
-int GetScaleOfExternalDisplay() {
-  // Converting scale to percents.
-  return floor(GetScale(GetExternalDisplayId()) * 100.0 + 0.5);
-}
-
-gfx::Size GetResolutionOfExternalDisplay() {
-  return GetResolution(GetExternalDisplayId());
-}
-
 PolicyValue GetPolicySetting() {
   const base::DictionaryValue* resolution_pref = nullptr;
   chromeos::CrosSettings::Get()->GetDictionary(
@@ -150,8 +95,8 @@
   return result;
 }
 
-void AddExternalDisplay() {
-  GetDisplayManager()->AddRemoveDisplay(
+void AddExternalDisplay(display::DisplayManager* display_manager) {
+  display_manager->AddRemoveDisplay(
       {display::ManagedDisplayMode(gfx::Size(800, 600), 30.0, false, false),
        display::ManagedDisplayMode(gfx::Size(800, 600), 60.0, false, false),
        display::ManagedDisplayMode(gfx::Size(1280, 800), 60.0, false, false),
@@ -188,12 +133,15 @@
       "{" + base::JoinString(json_entries, ",") + "}");
 }
 
-std::unique_ptr<extensions::api::system_display::DisplayMode>
-CreateDisplayMode(int64_t display_id, int width, int height) {
+std::unique_ptr<extensions::api::system_display::DisplayMode> CreateDisplayMode(
+    int64_t display_id,
+    int width,
+    int height,
+    const display::DisplayManager* display_manager) {
   auto result =
       std::make_unique<extensions::api::system_display::DisplayMode>();
   const display::ManagedDisplayInfo& info =
-      GetDisplayManager()->GetDisplayInfo(display_id);
+      display_manager->GetDisplayInfo(display_id);
   for (const display::ManagedDisplayMode& mode : info.display_modes()) {
     if (mode.size().width() == width && mode.size().height() == height) {
       result->width = mode.size().width();
@@ -216,7 +164,7 @@
 namespace policy {
 
 class DeviceDisplayResolutionTestBase
-    : public policy::DevicePolicyCrosBrowserTest,
+    : public policy::DeviceDisplayPolicyCrosBrowserTest,
       public testing::WithParamInterface<PolicyValue> {
  public:
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -225,45 +173,14 @@
     command_line->AppendSwitch(switches::kUseFirstDisplayAsInternal);
   }
 
-  void SetUpInProcessBrowserTestFixture() override {
-    ash::DisplayConfigurationController::DisableAnimatorForTest();
-    DevicePolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture();
-  }
-
-  void TearDownOnMainThread() override {
-    // If the login display is still showing, exit gracefully.
-    if (chromeos::LoginDisplayHost::default_host()) {
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&chrome::AttemptExit));
-      RunUntilBrowserProcessQuits();
-    }
-  }
-
  protected:
   DeviceDisplayResolutionTestBase() {}
 
   void SetPolicy(PolicyValue policy, bool recommended) {
     em::ChromeDeviceSettingsProto& proto(device_policy()->payload());
     SetPolicyValue(&proto, policy, recommended);
-    RefreshPolicyAndWaitUntilDeviceSettingsUpdated();
-  }
-
-  void UnsetPolicy() {
-    em::ChromeDeviceSettingsProto& proto(device_policy()->payload());
-    proto.clear_device_display_resolution();
-    RefreshPolicyAndWaitUntilDeviceSettingsUpdated();
-  }
-
-  void RefreshPolicyAndWaitUntilDeviceSettingsUpdated() {
-    base::RunLoop run_loop;
-    // For calls from SetPolicy().
-    std::unique_ptr<chromeos::CrosSettings::ObserverSubscription> observer =
-        chromeos::CrosSettings::Get()->AddSettingsObserver(
-            chromeos::kDeviceDisplayResolution, run_loop.QuitClosure());
-    RefreshDevicePolicy();
-    run_loop.Run();
-    // Allow tasks posted by CrosSettings observers to complete:
-    base::RunLoop().RunUntilIdle();
+    policy_helper()->RefreshPolicyAndWaitUntilDeviceSettingsUpdated(
+        {chromeos::kDeviceDisplayResolution});
   }
 
  private:
@@ -287,9 +204,10 @@
 IN_PROC_BROWSER_TEST_P(DeviceDisplayResolutionTest, DISABLED_Internal) {
   const PolicyValue policy_value = GetParam();
 
-  EXPECT_EQ(kDefaultDisplayScale, GetScaleOfInternalDisplay())
+  EXPECT_EQ(kDefaultDisplayScale, display_helper()->GetScaleOfFirstDisplay())
       << "Initial primary display scale before policy";
-  EXPECT_EQ(kDefaultDisplayResolution, GetResolutionOfInternalDisplay())
+  EXPECT_EQ(kDefaultDisplayResolution,
+            display_helper()->GetResolutionOfFirstDisplay())
       << "Initial primary display resolution before policy";
 
   SetPolicy(policy_value);
@@ -297,59 +215,61 @@
   EXPECT_EQ(policy_value, setting_resolution)
       << "Value of CrosSettings after policy value changed";
   EXPECT_EQ(policy_value.internal_scale_percentage.value_or(0),
-            GetScaleOfInternalDisplay())
+            display_helper()->GetScaleOfFirstDisplay())
       << "Scale of primary display after policy";
 }
 
 // crbug.com/1000694.
 IN_PROC_BROWSER_TEST_P(DeviceDisplayResolutionTest,
-                       DISABLED_ResizeExternalDisplay) {
+                       DISABLED_ResizeSecondDisplay) {
   const PolicyValue policy_value = GetParam();
 
-  AddExternalDisplay();
+  AddExternalDisplay(display_helper()->GetDisplayManager());
 
-  EXPECT_EQ(kDefaultDisplayScale, GetScaleOfInternalDisplay())
+  EXPECT_EQ(kDefaultDisplayScale, display_helper()->GetScaleOfFirstDisplay())
       << "Initial primary display scale after connecting external";
-  EXPECT_EQ(kDefaultDisplayResolution, GetResolutionOfInternalDisplay())
+  EXPECT_EQ(kDefaultDisplayResolution,
+            display_helper()->GetResolutionOfFirstDisplay())
       << "Initial primary display resolution after connecting external";
 
-  EXPECT_EQ(kDefaultDisplayScale, GetScaleOfExternalDisplay())
+  EXPECT_EQ(kDefaultDisplayScale, display_helper()->GetScaleOfSecondDisplay())
       << "Scale of external display before policy";
-  EXPECT_EQ(kDefaultExternalDisplayResolution, GetResolutionOfExternalDisplay())
+  EXPECT_EQ(kDefaultSecondDisplayResolution,
+            display_helper()->GetResolutionOfSecondDisplay())
       << "Resolution of external display before policy";
 
   SetPolicy(policy_value);
   EXPECT_EQ(policy_value.external_scale_percentage.value_or(0),
-            GetScaleOfExternalDisplay())
+            display_helper()->GetScaleOfSecondDisplay())
       << "Scale of already connected external display after policy";
   EXPECT_EQ(policy_value.external_display_size(),
-            GetResolutionOfExternalDisplay())
+            display_helper()->GetResolutionOfSecondDisplay())
       << "Resolution of already connected external display after policy";
 
   EXPECT_EQ(policy_value.internal_scale_percentage.value_or(0),
-            GetScaleOfInternalDisplay())
+            display_helper()->GetScaleOfFirstDisplay())
       << "Primary display scale after resizing external";
 }
 
 // crbug.com/1000694.
 IN_PROC_BROWSER_TEST_P(DeviceDisplayResolutionTest,
-                       DISABLED_ConnectExternalDisplay) {
+                       DISABLED_ConnectSecondDisplay) {
   const PolicyValue policy_value = GetParam();
 
   SetPolicy(policy_value);
   EXPECT_EQ(policy_value.internal_scale_percentage.value_or(0),
-            GetScaleOfInternalDisplay())
+            display_helper()->GetScaleOfFirstDisplay())
       << "Initial primary display scale after applying policy";
 
-  AddExternalDisplay();
+  AddExternalDisplay(display_helper()->GetDisplayManager());
   EXPECT_EQ(policy_value.external_scale_percentage.value_or(0),
-            GetScaleOfExternalDisplay())
+            display_helper()->GetScaleOfSecondDisplay())
       << "Scale of newly connected external display after policy";
   EXPECT_EQ(policy_value.external_display_size(),
-            GetResolutionOfExternalDisplay())
+            display_helper()->GetResolutionOfSecondDisplay())
       << "Resolution of newly connected external display after policy";
   EXPECT_EQ(policy_value.internal_scale_percentage.value_or(0),
-            GetScaleOfInternalDisplay())
+            display_helper()->GetScaleOfFirstDisplay())
       << "Primary display scale after connecting external";
 }
 
@@ -357,17 +277,17 @@
 IN_PROC_BROWSER_TEST_P(DeviceDisplayResolutionTest,
                        DISABLED_SetAndUnsetPolicy) {
   const PolicyValue policy_value = GetParam();
-  AddExternalDisplay();
+  AddExternalDisplay(display_helper()->GetDisplayManager());
   SetPolicy(policy_value);
-  UnsetPolicy();
+  policy_helper()->UnsetPolicy({chromeos::kDeviceDisplayResolution});
   EXPECT_EQ(policy_value.external_scale_percentage.value_or(0),
-            GetScaleOfExternalDisplay())
+            display_helper()->GetScaleOfSecondDisplay())
       << "Scale of the external display after policy was set and unset";
   EXPECT_EQ(policy_value.external_display_size(),
-            GetResolutionOfExternalDisplay())
+            display_helper()->GetResolutionOfSecondDisplay())
       << "Resolution of the external display after policy was set and unset.";
   EXPECT_EQ(policy_value.internal_scale_percentage.value_or(0),
-            GetScaleOfInternalDisplay())
+            display_helper()->GetScaleOfFirstDisplay())
       << "Initial primary display scale after policy was set and unset";
 }
 
@@ -405,13 +325,16 @@
     MixinBasedInProcessBrowserTest::SetUpInProcessBrowserTestFixture();
   }
 
-  policy::DevicePolicyCrosTestHelper test_helper_;
-
   chromeos::DeviceStateMixin device_state_{
       &mixin_host_,
       chromeos::DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED};
 
+  DeviceDisplayCrosTestHelper* display_helper() { return &helper_; }
+  DevicePolicyCrosTestHelper* policy_helper() { return &policy_helper_; }
+
  private:
+  DevicePolicyCrosTestHelper policy_helper_;
+  DeviceDisplayCrosTestHelper helper_;
   DISALLOW_COPY_AND_ASSIGN(DisplayResolutionBootTest);
 };
 
@@ -420,7 +343,7 @@
 
   // Set policy.
   policy::DevicePolicyBuilder* const device_policy(
-      test_helper_.device_policy());
+      policy_helper()->device_policy());
   em::ChromeDeviceSettingsProto& proto(device_policy->payload());
   SetPolicyValue(&proto, policy_value, true);
   base::RunLoop run_loop;
@@ -435,16 +358,16 @@
   run_loop.Run();
   // Allow tasks posted by CrosSettings observers to complete:
   base::RunLoop().RunUntilIdle();
-  AddExternalDisplay();
+  AddExternalDisplay(display_helper()->GetDisplayManager());
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(policy_value.external_scale_percentage.value_or(0),
-            GetScaleOfExternalDisplay())
+            display_helper()->GetScaleOfSecondDisplay())
       << "Scale of the external display after policy set";
   EXPECT_EQ(policy_value.external_display_size(),
-            GetResolutionOfExternalDisplay())
+            display_helper()->GetResolutionOfSecondDisplay())
       << "Resolution of the external display after policy set";
   EXPECT_EQ(policy_value.internal_scale_percentage.value_or(0),
-            GetScaleOfInternalDisplay())
+            display_helper()->GetScaleOfFirstDisplay())
       << "Initial primary display scale after policy set";
 }
 
@@ -452,17 +375,17 @@
 IN_PROC_BROWSER_TEST_P(DisplayResolutionBootTest, DISABLED_Reboot) {
   const PolicyValue policy_value = GetParam();
 
-  AddExternalDisplay();
+  AddExternalDisplay(display_helper()->GetDisplayManager());
   base::RunLoop().RunUntilIdle();
   // Check that the policy resolution is restored.
   EXPECT_EQ(policy_value.external_scale_percentage.value_or(0),
-            GetScaleOfExternalDisplay())
+            display_helper()->GetScaleOfSecondDisplay())
       << "Scale of the external display after reboot";
   EXPECT_EQ(policy_value.external_display_size(),
-            GetResolutionOfExternalDisplay())
+            display_helper()->GetResolutionOfSecondDisplay())
       << "Resolution of the external display after reboot";
   EXPECT_EQ(policy_value.internal_scale_percentage.value_or(0),
-            GetScaleOfInternalDisplay())
+            display_helper()->GetScaleOfFirstDisplay())
       << "Initial primary display scale after reboot";
 }
 
@@ -498,7 +421,10 @@
     run_loop.Run();
   }
 
+  DeviceDisplayCrosTestHelper* display_helper() { return &display_helper_; }
+
  private:
+  DeviceDisplayCrosTestHelper display_helper_;
   DISALLOW_COPY_AND_ASSIGN(DeviceDisplayResolutionRecommendedTest);
 };
 
@@ -506,9 +432,10 @@
 IN_PROC_BROWSER_TEST_P(DeviceDisplayResolutionRecommendedTest,
                        DISABLED_Internal) {
   const PolicyValue policy_value = GetParam();
-  EXPECT_EQ(kDefaultDisplayResolution, GetResolutionOfInternalDisplay())
+  EXPECT_EQ(kDefaultDisplayResolution,
+            display_helper()->GetResolutionOfFirstDisplay())
       << "Initial primary display resolution before policy";
-  EXPECT_EQ(kDefaultDisplayScale, GetScaleOfInternalDisplay())
+  EXPECT_EQ(kDefaultDisplayScale, display_helper()->GetScaleOfFirstDisplay())
       << "Initial primary display scale before policy";
 
   SetPolicy(policy_value);
@@ -516,45 +443,47 @@
   EXPECT_EQ(policy_value, setting_resolution)
       << "Value of CrosSettings after policy value changed";
   EXPECT_EQ(setting_resolution.internal_scale_percentage.value_or(0),
-            GetScaleOfInternalDisplay())
+            display_helper()->GetScaleOfFirstDisplay())
       << "Scale of primary display after policy";
 
   extensions::api::system_display::DisplayProperties props;
   double user_scale = 50;
   props.display_zoom_factor = std::make_unique<double>(user_scale / 100.0);
-  SetUserProperties(GetInternalDisplayId(), std::move(props));
+  SetUserProperties(display_helper()->GetFirstDisplayId(), std::move(props));
 
-  EXPECT_EQ(user_scale, GetScaleOfInternalDisplay())
+  EXPECT_EQ(user_scale, display_helper()->GetScaleOfFirstDisplay())
       << "Scale of internal display after user operation";
 }
 
 // crbug.com/1000694.
 IN_PROC_BROWSER_TEST_P(DeviceDisplayResolutionRecommendedTest,
-                       DISABLED_ResizeExternalDisplay) {
+                       DISABLED_ResizeSecondDisplay) {
   const PolicyValue policy_value = GetParam();
-  AddExternalDisplay();
+  AddExternalDisplay(display_helper()->GetDisplayManager());
 
-  EXPECT_EQ(kDefaultDisplayScale, GetScaleOfInternalDisplay())
+  EXPECT_EQ(kDefaultDisplayScale, display_helper()->GetScaleOfFirstDisplay())
       << "Initial primary display scale after connecting external";
-  EXPECT_EQ(kDefaultDisplayResolution, GetResolutionOfInternalDisplay())
+  EXPECT_EQ(kDefaultDisplayResolution,
+            display_helper()->GetResolutionOfFirstDisplay())
       << "Initial primary display resolution after connecting external";
 
-  EXPECT_EQ(kDefaultDisplayScale, GetScaleOfExternalDisplay())
+  EXPECT_EQ(kDefaultDisplayScale, display_helper()->GetScaleOfSecondDisplay())
       << "Scale of external display before policy";
-  EXPECT_EQ(kDefaultExternalDisplayResolution, GetResolutionOfExternalDisplay())
+  EXPECT_EQ(kDefaultSecondDisplayResolution,
+            display_helper()->GetResolutionOfSecondDisplay())
       << "Resolution of external display before policy";
 
   SetPolicy(policy_value);
 
   EXPECT_EQ(policy_value.external_scale_percentage.value_or(0),
-            GetScaleOfExternalDisplay())
+            display_helper()->GetScaleOfSecondDisplay())
       << "Scale of the external display after policy";
   EXPECT_EQ(policy_value.external_display_size(),
-            GetResolutionOfExternalDisplay())
+            display_helper()->GetResolutionOfSecondDisplay())
       << "Resolution the external display after policy";
 
   EXPECT_EQ(policy_value.internal_scale_percentage.value_or(0),
-            GetScaleOfInternalDisplay())
+            display_helper()->GetScaleOfFirstDisplay())
       << "Internal display scale after resizing external";
 
   extensions::api::system_display::DisplayProperties props;
@@ -563,17 +492,18 @@
   double user_height = 1080;
   props.display_zoom_factor = std::make_unique<double>(user_scale / 100.0);
   props.display_mode =
-      CreateDisplayMode(GetExternalDisplayId(), user_width, user_height);
-  SetUserProperties(GetExternalDisplayId(), std::move(props));
+      CreateDisplayMode(display_helper()->GetSecondDisplayId(), user_width,
+                        user_height, display_helper()->GetDisplayManager());
+  SetUserProperties(display_helper()->GetSecondDisplayId(), std::move(props));
 
-  EXPECT_EQ(user_scale, GetScaleOfExternalDisplay())
+  EXPECT_EQ(user_scale, display_helper()->GetScaleOfSecondDisplay())
       << "Scale of the external display after user operation";
   EXPECT_EQ(gfx::Size(user_width, user_height),
-            GetResolutionOfExternalDisplay())
+            display_helper()->GetResolutionOfSecondDisplay())
       << "Resolution of the external display after user operation";
 
   EXPECT_EQ(policy_value.internal_scale_percentage.value_or(0),
-            GetScaleOfInternalDisplay())
+            display_helper()->GetScaleOfFirstDisplay())
       << "Internal display scale after user operation";
 }
 
diff --git a/chrome/browser/chromeos/policy/display_rotation_default_handler_browsertest.cc b/chrome/browser/chromeos/policy/display_rotation_default_handler_browsertest.cc
index 142b3b6e..d74c20b 100644
--- a/chrome/browser/chromeos/policy/display_rotation_default_handler_browsertest.cc
+++ b/chrome/browser/chromeos/policy/display_rotation_default_handler_browsertest.cc
@@ -16,129 +16,42 @@
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/chromeos/login/test/device_state_mixin.h"
-#include "chrome/browser/chromeos/login/ui/login_display_host.h"
-#include "chrome/browser/chromeos/policy/device_policy_builder.h"
-#include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
-#include "chrome/browser/chromeos/settings/cros_settings.h"
-#include "chrome/browser/lifetime/application_lifetime.h"
-#include "chrome/test/base/mixin_based_in_process_browser_test.h"
+#include "chrome/browser/chromeos/policy/device_display_cros_browser_test.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/dbus/session_manager/fake_session_manager_client.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
-#include "chromeos/settings/cros_settings_names.h"
-#include "components/policy/proto/chrome_device_policy.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "ui/display/display.h"
 #include "ui/display/display_layout.h"
 #include "ui/display/manager/display_manager.h"
 
 namespace em = enterprise_management;
 
-namespace {
-
-display::DisplayManager* GetDisplayManager() {
-  return ash::Shell::Get()->display_manager();
-}
-
-display::Display::Rotation GetRotationOfFirstDisplay() {
-  const display::DisplayManager* const display_manager = GetDisplayManager();
-  const int64_t first_display_id = display_manager->first_display_id();
-  const display::Display& first_display =
-      display_manager->GetDisplayForId(first_display_id);
-  return first_display.rotation();
-}
-
-// Fails the test and returns ROTATE_0 if there is no second display.
-display::Display::Rotation GetRotationOfSecondDisplay() {
-  const display::DisplayManager* const display_manager = GetDisplayManager();
-  if (display_manager->GetNumDisplays() < 2) {
-    ADD_FAILURE()
-        << "Requested rotation of second display while there was only one.";
-    return display::Display::ROTATE_0;
-  }
-  const display::DisplayIdList display_id_pair =
-      display_manager->GetCurrentDisplayIdList();
-  const display::Display& second_display =
-      display_manager->GetDisplayForId(display_id_pair[1]);
-  return second_display.rotation();
-}
-
-} // anonymous namespace
-
 namespace policy {
 
 class DisplayRotationDefaultTest
-    : public policy::DevicePolicyCrosBrowserTest,
+    : public DeviceDisplayPolicyCrosBrowserTest,
       public testing::WithParamInterface<display::Display::Rotation> {
- public:
-  DisplayRotationDefaultTest() {}
-
+ protected:
   void SetUpCommandLine(base::CommandLine* command_line) override {
     command_line->AppendSwitch(chromeos::switches::kLoginManager);
     command_line->AppendSwitch(chromeos::switches::kForceLoginManagerInTests);
   }
 
-  void SetUpInProcessBrowserTestFixture() override {
-    ash::DisplayConfigurationController::DisableAnimatorForTest();
-    DevicePolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture();
-  }
-
-  void TearDownOnMainThread() override {
-    // If the login display is still showing, exit gracefully.
-    if (chromeos::LoginDisplayHost::default_host()) {
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&chrome::AttemptExit));
-      RunUntilBrowserProcessQuits();
-    }
-  }
-
- protected:
-  void SetPolicy(int rotation) {
+  void SetRotationPolicy(int rotation) {
     em::ChromeDeviceSettingsProto& proto(device_policy()->payload());
     proto.mutable_display_rotation_default()->set_display_rotation_default(
         static_cast<em::DisplayRotationDefaultProto::Rotation>(rotation));
-    RefreshPolicyAndWaitUntilDeviceSettingsUpdated();
+    policy_helper()->RefreshPolicyAndWaitUntilDeviceSettingsUpdated(
+        {chromeos::kDisplayRotationDefault, chromeos::kSystemUse24HourClock});
   }
 
-  // This is needed to test that refreshing the settings without change to the
-  // DisplayRotationDefault policy will not rotate the display again.
-  void SetADifferentPolicy() {
+  void RetriggerRotationPolicy() {
     em::ChromeDeviceSettingsProto& proto(device_policy()->payload());
     const bool clock24 = proto.use_24hour_clock().use_24hour_clock();
     proto.mutable_use_24hour_clock()->set_use_24hour_clock(!clock24);
-    RefreshPolicyAndWaitUntilDeviceSettingsUpdated();
+    policy_helper()->RefreshPolicyAndWaitUntilDeviceSettingsUpdated(
+        {chromeos::kDisplayRotationDefault, chromeos::kSystemUse24HourClock});
   }
-
-  void UnsetPolicy() {
-    em::ChromeDeviceSettingsProto& proto(device_policy()->payload());
-    proto.clear_display_rotation_default();
-    RefreshPolicyAndWaitUntilDeviceSettingsUpdated();
-  }
-
-  // Creates second display if there is none yet, or removes it if there is one.
-  void ToggleSecondDisplay() {
-    GetDisplayManager()->AddRemoveDisplay();
-    base::RunLoop().RunUntilIdle();
-  }
-
-  void RefreshPolicyAndWaitUntilDeviceSettingsUpdated() {
-    base::RunLoop run_loop;
-    // For calls from SetPolicy().
-    std::unique_ptr<chromeos::CrosSettings::ObserverSubscription> observer =
-        chromeos::CrosSettings::Get()->AddSettingsObserver(
-            chromeos::kDisplayRotationDefault, run_loop.QuitClosure());
-    // For calls from SetADifferentPolicy().
-    std::unique_ptr<chromeos::CrosSettings::ObserverSubscription> observer2 =
-        chromeos::CrosSettings::Get()->AddSettingsObserver(
-            chromeos::kSystemUse24HourClock, run_loop.QuitClosure());
-    RefreshDevicePolicy();
-    run_loop.Run();
-    // Allow tasks posted by CrosSettings observers to complete:
-    base::RunLoop().RunUntilIdle();
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(DisplayRotationDefaultTest);
 };
 
 // If display::Display::Rotation is ever modified and this test fails, there are
@@ -164,65 +77,71 @@
 
 IN_PROC_BROWSER_TEST_P(DisplayRotationDefaultTest, FirstDisplay) {
   const display::Display::Rotation policy_rotation = GetParam();
-  EXPECT_EQ(display::Display::ROTATE_0, GetRotationOfFirstDisplay())
+  EXPECT_EQ(display::Display::ROTATE_0,
+            display_helper()->GetRotationOfFirstDisplay())
       << "Initial primary rotation before policy";
 
-  SetPolicy(policy_rotation);
+  SetRotationPolicy(policy_rotation);
   int settings_rotation;
   EXPECT_TRUE(chromeos::CrosSettings::Get()->GetInteger(
       chromeos::kDisplayRotationDefault, &settings_rotation));
   EXPECT_EQ(policy_rotation, settings_rotation)
       << "Value of CrosSettings after policy value changed";
-  EXPECT_EQ(policy_rotation, GetRotationOfFirstDisplay())
+  EXPECT_EQ(policy_rotation, display_helper()->GetRotationOfFirstDisplay())
       << "Rotation of primary display after policy";
 }
 
 IN_PROC_BROWSER_TEST_P(DisplayRotationDefaultTest, RefreshSecondDisplay) {
   const display::Display::Rotation policy_rotation = GetParam();
-  ToggleSecondDisplay();
-  EXPECT_EQ(display::Display::ROTATE_0, GetRotationOfSecondDisplay())
+  display_helper()->ToggleSecondDisplay();
+  EXPECT_EQ(display::Display::ROTATE_0,
+            display_helper()->GetRotationOfSecondDisplay())
       << "Rotation of secondary display before policy";
-  SetPolicy(policy_rotation);
-  EXPECT_EQ(policy_rotation, GetRotationOfSecondDisplay())
+  SetRotationPolicy(policy_rotation);
+  EXPECT_EQ(policy_rotation, display_helper()->GetRotationOfSecondDisplay())
       << "Rotation of already connected secondary display after policy";
 }
 
 IN_PROC_BROWSER_TEST_P(DisplayRotationDefaultTest, ConnectSecondDisplay) {
   const display::Display::Rotation policy_rotation = GetParam();
-  SetPolicy(policy_rotation);
-  ToggleSecondDisplay();
-  EXPECT_EQ(policy_rotation, GetRotationOfFirstDisplay())
+  SetRotationPolicy(policy_rotation);
+  display_helper()->ToggleSecondDisplay();
+  EXPECT_EQ(policy_rotation, display_helper()->GetRotationOfFirstDisplay())
       << "Rotation of primary display after policy";
-  EXPECT_EQ(policy_rotation, GetRotationOfSecondDisplay())
+  EXPECT_EQ(policy_rotation, display_helper()->GetRotationOfSecondDisplay())
       << "Rotation of newly connected secondary display after policy";
 }
 
+// This test is needed to test that refreshing the settings without change to
+// the DisplayRotationDefault policy will not rotate the display again because
+// it was changed by the user.
 IN_PROC_BROWSER_TEST_P(DisplayRotationDefaultTest, UserInteraction) {
   const display::Display::Rotation policy_rotation = GetParam();
   const display::Display::Rotation user_rotation = display::Display::ROTATE_90;
-  GetDisplayManager()->SetDisplayRotation(
-      GetDisplayManager()->first_display_id(), user_rotation,
+  display_helper()->GetDisplayManager()->SetDisplayRotation(
+      display_helper()->GetDisplayManager()->first_display_id(), user_rotation,
       display::Display::RotationSource::USER);
-  EXPECT_EQ(user_rotation, GetRotationOfFirstDisplay())
+  EXPECT_EQ(user_rotation, display_helper()->GetRotationOfFirstDisplay())
       << "Rotation of primary display after user change";
-  SetPolicy(policy_rotation);
-  EXPECT_EQ(policy_rotation, GetRotationOfFirstDisplay())
+  SetRotationPolicy(policy_rotation);
+  EXPECT_EQ(policy_rotation, display_helper()->GetRotationOfFirstDisplay())
       << "Rotation of primary display after policy overrode user change";
-  GetDisplayManager()->SetDisplayRotation(
-      GetDisplayManager()->first_display_id(), user_rotation,
+  display_helper()->GetDisplayManager()->SetDisplayRotation(
+      display_helper()->GetDisplayManager()->first_display_id(), user_rotation,
       display::Display::RotationSource::USER);
-  EXPECT_EQ(user_rotation, GetRotationOfFirstDisplay())
+  EXPECT_EQ(user_rotation, display_helper()->GetRotationOfFirstDisplay())
       << "Rotation of primary display after user overrode policy change";
-  SetADifferentPolicy();
-  EXPECT_EQ(user_rotation, GetRotationOfFirstDisplay())
+  RetriggerRotationPolicy();
+  EXPECT_EQ(user_rotation, display_helper()->GetRotationOfFirstDisplay())
       << "Rotation of primary display after policy reloaded without change";
 }
 
 IN_PROC_BROWSER_TEST_P(DisplayRotationDefaultTest, SetAndUnsetPolicy) {
   const display::Display::Rotation policy_rotation = GetParam();
-  SetPolicy(policy_rotation);
-  UnsetPolicy();
-  EXPECT_EQ(policy_rotation, GetRotationOfFirstDisplay())
+  SetRotationPolicy(policy_rotation);
+  policy_helper()->UnsetPolicy(
+      {chromeos::kDisplayRotationDefault, chromeos::kSystemUse24HourClock});
+  EXPECT_EQ(policy_rotation, display_helper()->GetRotationOfFirstDisplay())
       << "Rotation of primary display after policy was set and removed.";
 }
 
@@ -230,12 +149,13 @@
                        SetAndUnsetPolicyWithUserInteraction) {
   const display::Display::Rotation policy_rotation = GetParam();
   const display::Display::Rotation user_rotation = display::Display::ROTATE_90;
-  SetPolicy(policy_rotation);
-  GetDisplayManager()->SetDisplayRotation(
-      GetDisplayManager()->first_display_id(), user_rotation,
+  SetRotationPolicy(policy_rotation);
+  display_helper()->GetDisplayManager()->SetDisplayRotation(
+      display_helper()->GetDisplayManager()->first_display_id(), user_rotation,
       display::Display::RotationSource::USER);
-  UnsetPolicy();
-  EXPECT_EQ(user_rotation, GetRotationOfFirstDisplay())
+  policy_helper()->UnsetPolicy(
+      {chromeos::kDisplayRotationDefault, chromeos::kSystemUse24HourClock});
+  EXPECT_EQ(user_rotation, display_helper()->GetRotationOfFirstDisplay())
       << "Rotation of primary display after policy was set to "
       << policy_rotation << ", user changed the rotation to " << user_rotation
       << ", and policy was removed.";
@@ -265,19 +185,21 @@
   ~DisplayRotationBootTest() override = default;
 
   void SetUpInProcessBrowserTestFixture() override {
-    // Override FakeSessionManagerClient. This will be shut down by the browser.
     chromeos::SessionManagerClient::InitializeFakeInMemory();
     ash::DisplayConfigurationController::DisableAnimatorForTest();
     MixinBasedInProcessBrowserTest::SetUpInProcessBrowserTestFixture();
   }
 
-  policy::DevicePolicyCrosTestHelper test_helper_;
-
   chromeos::DeviceStateMixin device_state_{
       &mixin_host_,
       chromeos::DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED};
 
+  DevicePolicyCrosTestHelper* policy_helper() { return &policy_helper_; }
+  DeviceDisplayCrosTestHelper* display_helper() { return &display_helper_; }
+
  private:
+  DevicePolicyCrosTestHelper policy_helper_;
+  DeviceDisplayCrosTestHelper display_helper_;
   DISALLOW_COPY_AND_ASSIGN(DisplayRotationBootTest);
 };
 
@@ -287,7 +209,7 @@
 
   // Set policy.
   policy::DevicePolicyBuilder* const device_policy(
-      test_helper_.device_policy());
+      policy_helper()->device_policy());
   em::ChromeDeviceSettingsProto& proto(device_policy->payload());
   proto.mutable_display_rotation_default()->set_display_rotation_default(
       static_cast<em::DisplayRotationDefaultProto::Rotation>(policy_rotation));
@@ -305,25 +227,22 @@
   base::RunLoop().RunUntilIdle();
 
   // Check the display's rotation.
-  display::DisplayManager* const display_manager = GetDisplayManager();
-  const int64_t first_display_id = display_manager->first_display_id();
-  const display::Display& first_display =
-      display_manager->GetDisplayForId(first_display_id);
-  EXPECT_EQ(policy_rotation, first_display.rotation());
+  EXPECT_EQ(policy_rotation, display_helper()->GetRotationOfFirstDisplay());
 
   // Let the user rotate the display to a different orientation, to check that
   // the policy value is restored after reboot.
-  display_manager->SetDisplayRotation(first_display_id, user_rotation,
-                                      display::Display::RotationSource::USER);
+  display_helper()->GetDisplayManager()->SetDisplayRotation(
+      display_helper()->GetFirstDisplayId(), user_rotation,
+      display::Display::RotationSource::USER);
   base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(user_rotation, first_display.rotation());
+  EXPECT_EQ(user_rotation, display_helper()->GetRotationOfFirstDisplay());
 }
 
 IN_PROC_BROWSER_TEST_P(DisplayRotationBootTest, Reboot) {
   const display::Display::Rotation policy_rotation = GetParam();
 
   // Check that the policy rotation is restored.
-  EXPECT_EQ(policy_rotation, GetRotationOfFirstDisplay());
+  EXPECT_EQ(policy_rotation, display_helper()->GetRotationOfFirstDisplay());
 }
 
 INSTANTIATE_TEST_SUITE_P(PolicyDisplayRotationDefault,
diff --git a/chrome/browser/chromeos/system/device_disabling_browsertest.cc b/chrome/browser/chromeos/system/device_disabling_browsertest.cc
index 50884201..3fe952c3 100644
--- a/chrome/browser/chromeos/system/device_disabling_browsertest.cc
+++ b/chrome/browser/chromeos/system/device_disabling_browsertest.cc
@@ -5,12 +5,17 @@
 #include <memory>
 #include <string>
 
+#include "ash/public/cpp/login_screen_test_api.h"
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/run_loop.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_platform_part.h"
+#include "chrome/browser/chromeos/login/login_wizard.h"
 #include "chrome/browser/chromeos/login/test/device_state_mixin.h"
+#include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
 #include "chrome/browser/chromeos/login/test/network_portal_detector_mixin.h"
 #include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
@@ -19,11 +24,13 @@
 #include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/chromeos/system/device_disabling_manager.h"
 #include "chrome/browser/ui/webui/chromeos/login/device_disabled_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/network_state_informer.h"
 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
 #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
+#include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/session_manager/fake_session_manager_client.h"
 #include "chromeos/dbus/shill/fake_shill_manager_client.h"
@@ -140,6 +147,7 @@
 IN_PROC_BROWSER_TEST_F(DeviceDisablingTest, DisableDuringNormalOperation) {
   MarkDisabledAndWaitForPolicyFetch();
   EXPECT_TRUE(DeviceDisabledScreenShown());
+  EXPECT_TRUE(ash::LoginScreenTestApi::IsOobeDialogVisible());
 }
 
 // Verifies that device disabling works when the ephemeral users policy is
@@ -233,6 +241,45 @@
 IN_PROC_BROWSER_TEST_F(PresetPolicyDeviceDisablingTest,
                        DisableBeforeStartup) {
   EXPECT_TRUE(DeviceDisabledScreenShown());
+  EXPECT_TRUE(ash::LoginScreenTestApi::IsOobeDialogVisible());
+}
+
+class DeviceDisablingBeforeLoginHostCreated
+    : public PresetPolicyDeviceDisablingTest {
+ public:
+  DeviceDisablingBeforeLoginHostCreated() {
+    // Start with user pods.
+    login_mixin_.AppendManagedUsers(2);
+  }
+
+  bool SetUpUserDataDirectory() override {
+    // LoginManagerMixin sets up command line in the SetUpUserDataDirectory.
+    if (!PresetPolicyDeviceDisablingTest::SetUpUserDataDirectory())
+      return false;
+    // Postpone login host creation.
+    base::CommandLine::ForCurrentProcess()->RemoveSwitch(
+        chromeos::switches::kForceLoginManagerInTests);
+    return true;
+  }
+
+  bool ShouldWaitForOobeUI() override { return false; }
+
+ protected:
+  LoginManagerMixin login_mixin_{&mixin_host_};
+};
+
+// Sometimes LoginHost creation postponed (e.g. due to language switch
+// https://crbug.com/1065569). This tests checks this flow.
+IN_PROC_BROWSER_TEST_F(DeviceDisablingBeforeLoginHostCreated,
+                       ShowsDisabledScreen) {
+  EXPECT_TRUE(
+      system::DeviceDisablingManager::IsDeviceDisabledDuringNormalOperation());
+  EXPECT_EQ(nullptr, LoginDisplayHost::default_host());
+  EXPECT_NE(nullptr,
+            g_browser_process->platform_part()->device_disabling_manager());
+  ShowLoginWizard(OobeScreen::SCREEN_UNKNOWN);
+  OobeScreenWaiter(DeviceDisabledScreenView::kScreenId).Wait();
+  EXPECT_TRUE(ash::LoginScreenTestApi::IsOobeDialogVisible());
 }
 
 }  // namespace system
diff --git a/chrome/browser/chromeos/system/device_disabling_manager_default_delegate.cc b/chrome/browser/chromeos/system/device_disabling_manager_default_delegate.cc
index 0e733ac..fd2454d 100644
--- a/chrome/browser/chromeos/system/device_disabling_manager_default_delegate.cc
+++ b/chrome/browser/chromeos/system/device_disabling_manager_default_delegate.cc
@@ -20,6 +20,10 @@
 }
 
 void DeviceDisablingManagerDefaultDelegate::ShowDeviceDisabledScreen() {
+  if (!LoginDisplayHost::default_host()) {
+    // LoginDisplayHost will check if the device is disabled on creation.
+    return;
+  }
   LoginDisplayHost::default_host()->StartWizard(
       DeviceDisabledScreenView::kScreenId);
 }
diff --git a/chrome/browser/downgrade/downgrade_manager.cc b/chrome/browser/downgrade/downgrade_manager.cc
index fa3fca1..96fb7cb 100644
--- a/chrome/browser/downgrade/downgrade_manager.cc
+++ b/chrome/browser/downgrade/downgrade_manager.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/downgrade/snapshot_manager.h"
 #include "chrome/browser/downgrade/user_data_downgrade.h"
 #include "chrome/browser/policy/browser_dm_token_storage.h"
+#include "chrome/common/channel_info.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
@@ -222,30 +223,36 @@
     return type_ == Type::kAdministrativeWipe;
   }
 
-  auto current_milestone = current_version.components()[0];
-  auto last_milestone = last_version->components()[0];
+  if (current_version == *last_version)
+    return false;  // Nothing to do if the version has not changed.
 
-  // Take a snapshot on the first launch after a major version jump.
-  if (current_milestone > last_milestone) {
-    const int max_number_of_snapshots =
-        g_browser_process->local_state()->GetInteger(
-            prefs::kUserDataSnapshotRentionLimit);
-    SnapshotManager snapshot_manager(user_data_dir);
-    if (max_number_of_snapshots > 0)
-      snapshot_manager.TakeSnapshot(*last_version);
-    snapshot_manager.PurgeInvalidAndOldSnapshots(max_number_of_snapshots);
-    return false;
+  if (current_version < *last_version) {
+    type_ = GetDowngradeTypeWithSnapshot(user_data_dir, current_version,
+                                         *last_version);
+    if (type_ != Type::kNone)
+      base::UmaHistogramEnumeration("Downgrade.Type", type_);
+
+    return type_ == Type::kAdministrativeWipe ||
+           type_ == Type::kSnapshotRestore;
   }
 
-  if (current_version >= *last_version)
-    return false;  // Same version or mid-milestone upgrade.
-
-  type_ = GetDowngradeTypeWithSnapshot(user_data_dir, current_version,
-                                       *last_version);
-  if (type_ != Type::kNone)
-    base::UmaHistogramEnumeration("Downgrade.Type", type_);
-
-  return type_ == Type::kAdministrativeWipe || type_ == Type::kSnapshotRestore;
+  auto current_milestone = current_version.components()[0];
+  int max_number_of_snapshots = g_browser_process->local_state()->GetInteger(
+      prefs::kUserDataSnapshotRentionLimit);
+  base::Optional<uint32_t> purge_milestone;
+  if (current_milestone == last_version->components()[0]) {
+    // Mid-milestone snapshots are only taken on canary installs.
+    if (chrome::GetChannel() != version_info::Channel::CANARY)
+      return false;
+    // Keep one snapshot in this milestone unless snapshots are disabled.
+    max_number_of_snapshots = std::min(max_number_of_snapshots, 1);
+    purge_milestone = current_milestone;
+  }
+  SnapshotManager snapshot_manager(user_data_dir);
+  snapshot_manager.TakeSnapshot(*last_version);
+  snapshot_manager.PurgeInvalidAndOldSnapshots(max_number_of_snapshots,
+                                               purge_milestone);
+  return false;
 }
 
 void DowngradeManager::UpdateLastVersion(const base::FilePath& user_data_dir) {
diff --git a/chrome/browser/downgrade/snapshot_manager.cc b/chrome/browser/downgrade/snapshot_manager.cc
index fa211cf..5f178d9 100644
--- a/chrome/browser/downgrade/snapshot_manager.cc
+++ b/chrome/browser/downgrade/snapshot_manager.cc
@@ -313,7 +313,8 @@
 }
 
 void SnapshotManager::PurgeInvalidAndOldSnapshots(
-    int max_number_of_snapshots) const {
+    int max_number_of_snapshots,
+    base::Optional<uint32_t> milestone) const {
   const auto snapshot_dir = user_data_dir_.Append(kSnapshotsDir);
 
   // Move the invalid snapshots within from Snapshots/NN to Snapshots.DELETE/NN.
@@ -329,7 +330,17 @@
                                "Downgrade.InvalidSnapshotMove.FailureCount");
   }
 
-  auto available_snapshots = GetAvailableSnapshots(snapshot_dir);
+  base::flat_set<base::Version> available_snapshots =
+      GetAvailableSnapshots(snapshot_dir);
+  if (milestone.has_value()) {
+    // Only consider versions for the specified milestone.
+    available_snapshots.erase(available_snapshots.upper_bound(
+                                  base::Version({*milestone + 1, 0, 0, 0})),
+                              available_snapshots.end());
+    available_snapshots.erase(
+        available_snapshots.begin(),
+        available_snapshots.lower_bound(base::Version({*milestone, 0, 0, 0})));
+  }
 
   if (available_snapshots.size() <=
       base::checked_cast<size_t>(max_number_of_snapshots)) {
diff --git a/chrome/browser/downgrade/snapshot_manager.h b/chrome/browser/downgrade/snapshot_manager.h
index f989ba8..f0102576 100644
--- a/chrome/browser/downgrade/snapshot_manager.h
+++ b/chrome/browser/downgrade/snapshot_manager.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/files/file_path.h"
+#include "base/optional.h"
 #include "base/time/time.h"
 #include "base/version.h"
 
@@ -42,8 +43,10 @@
   void RestoreSnapshot(const base::Version& version);
 
   // Keeps the number of snapshots on the disk under |max_number_of_snapshots|
-  // by moving invalid and older snapshots for later deletion.
-  void PurgeInvalidAndOldSnapshots(int max_number_of_snapshots) const;
+  // by moving invalid and older snapshots for later deletion. If |milestone| is
+  // specified, limit the deletion to the snapshots from that milestone.
+  void PurgeInvalidAndOldSnapshots(int max_number_of_snapshots,
+                                   base::Optional<uint32_t> milestone) const;
 
   // Deletes snapshot data created after |delete_begin| for |profile_base_name|.
   // |remove_mask| (of bits from ChromeBrowsingDataRemoverDelegate::DataType)
diff --git a/chrome/browser/downgrade/snapshot_manager_unittest.cc b/chrome/browser/downgrade/snapshot_manager_unittest.cc
index 2f017dcb..83c11e60 100644
--- a/chrome/browser/downgrade/snapshot_manager_unittest.cc
+++ b/chrome/browser/downgrade/snapshot_manager_unittest.cc
@@ -383,7 +383,8 @@
 
   int max_number_of_snapshots = 3;
   SnapshotManager snapshot_manager(user_data_dir());
-  snapshot_manager.PurgeInvalidAndOldSnapshots(max_number_of_snapshots);
+  snapshot_manager.PurgeInvalidAndOldSnapshots(max_number_of_snapshots,
+                                               base::nullopt);
 
   const base::FilePath deletion_directory =
       user_data_dir()
@@ -418,12 +419,38 @@
                base::File::FLAG_CREATE | base::File::FLAG_WRITE);
   }
 
-  size_t max_number_of_snapshots = 3;
+  int max_number_of_snapshots = 3;
   SnapshotManager snapshot_manager(user_data_dir());
-  snapshot_manager.PurgeInvalidAndOldSnapshots(max_number_of_snapshots);
+  snapshot_manager.PurgeInvalidAndOldSnapshots(max_number_of_snapshots,
+                                               base::nullopt);
 
   for (const auto& path : valid_snapshot_paths)
     EXPECT_TRUE(base::PathExists(path));
 }
 
+TEST_F(SnapshotManagerTest,
+       PurgeInvalidAndOldSnapshotsKeepsValidSnapshotsPerMilestone) {
+  std::vector<base::FilePath> valid_snapshot_paths{
+      user_data_dir().Append(kSnapshotsDir).AppendASCII("19.0.0"),
+      user_data_dir().Append(kSnapshotsDir).AppendASCII("20.0.0"),
+      user_data_dir().Append(kSnapshotsDir).AppendASCII("20.0.1"),
+      user_data_dir().Append(kSnapshotsDir).AppendASCII("21.0.1"),
+  };
+
+  for (const auto& path : valid_snapshot_paths) {
+    ASSERT_TRUE(base::CreateDirectory(path));
+    base::File(path.Append(kDowngradeLastVersionFile),
+               base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+  }
+
+  int max_number_of_snapshots = 1;
+  SnapshotManager snapshot_manager(user_data_dir());
+  snapshot_manager.PurgeInvalidAndOldSnapshots(max_number_of_snapshots, 20);
+
+  EXPECT_TRUE(base::PathExists(valid_snapshot_paths[0]));
+  EXPECT_FALSE(base::PathExists(valid_snapshot_paths[1]));
+  EXPECT_TRUE(base::PathExists(valid_snapshot_paths[2]));
+  EXPECT_TRUE(base::PathExists(valid_snapshot_paths[3]));
+}
+
 }  // namespace downgrade
diff --git a/chrome/browser/downgrade/user_data_downgrade.cc b/chrome/browser/downgrade/user_data_downgrade.cc
index 2d03a50..71d911a 100644
--- a/chrome/browser/downgrade/user_data_downgrade.cc
+++ b/chrome/browser/downgrade/user_data_downgrade.cc
@@ -111,6 +111,7 @@
 base::Optional<base::Version> GetSnapshotToRestore(
     const base::Version& version,
     const base::FilePath& user_data_dir) {
+  DCHECK(version.IsValid());
   base::FilePath top_snapshot_dir = user_data_dir.Append(kSnapshotsDir);
   auto available_snapshots = GetAvailableSnapshots(top_snapshot_dir);
 
diff --git a/chrome/browser/downgrade/user_data_downgrade.h b/chrome/browser/downgrade/user_data_downgrade.h
index 16bbfe2c2..713dbe0f 100644
--- a/chrome/browser/downgrade/user_data_downgrade.h
+++ b/chrome/browser/downgrade/user_data_downgrade.h
@@ -40,7 +40,7 @@
 // within the User Data directory).
 base::FilePath GetDiskCacheDir();
 
-// Returns the milestones that have a complete snapshot available.
+// Returns the versions that have a complete snapshot available.
 base::flat_set<base::Version> GetAvailableSnapshots(
     const base::FilePath& snapshot_dir);
 
@@ -50,7 +50,7 @@
     const base::FilePath& snapshot_dir);
 
 // Return the highest available snapshot version that is not greater than
-// |milestone|.
+// |version|.
 base::Optional<base::Version> GetSnapshotToRestore(
     const base::Version& version,
     const base::FilePath& user_data_dir);
diff --git a/chrome/browser/downgrade/user_data_snapshot_browsertest.cc b/chrome/browser/downgrade/user_data_snapshot_browsertest.cc
index d5270b7..15a2e24 100644
--- a/chrome/browser/downgrade/user_data_snapshot_browsertest.cc
+++ b/chrome/browser/downgrade/user_data_snapshot_browsertest.cc
@@ -18,6 +18,7 @@
 #include "base/test/mock_callback.h"
 #include "base/time/time.h"
 #include "base/version.h"
+#include "build/branding_buildflags.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/downgrade/downgrade_manager.h"
 #include "chrome/browser/first_run/scoped_relaunch_chrome_browser_override.h"
@@ -45,6 +46,12 @@
 #include "ui/base/page_transition_types.h"
 #include "ui/base/window_open_disposition.h"
 
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+#include "base/threading/thread_restrictions.h"
+#include "chrome/install_static/install_modes.h"
+#include "chrome/install_static/test/scoped_install_details.h"
+#endif
+
 namespace downgrade {
 
 namespace {
@@ -417,4 +424,81 @@
 IN_PROC_BROWSER_TEST_F(TabsSnapshotTest, PRE_Test) {}
 IN_PROC_BROWSER_TEST_F(TabsSnapshotTest, Test) {}
 
+// Tests that Google Chrome does not takes snapshots on mid-milestone updates.
+IN_PROC_BROWSER_TEST_F(InProcessBrowserTest, SameMilestoneSnapshot) {
+  DowngradeManager::EnableSnapshotsForTesting(true);
+  base::ScopedAllowBlockingForTesting scoped_allow_blocking;
+  base::FilePath user_data_dir;
+  ASSERT_TRUE(base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
+  auto current_version = version_info::GetVersion().GetString();
+
+  downgrade::DowngradeManager downgrade_manager;
+
+  // No snapshots for same version.
+  base::WriteFile(user_data_dir.Append(kDowngradeLastVersionFile),
+                  current_version.c_str(), current_version.size());
+  EXPECT_FALSE(downgrade_manager.PrepareUserDataDirectoryForCurrentVersion(
+      user_data_dir));
+  EXPECT_FALSE(
+      base::PathExists(user_data_dir.Append(downgrade::kSnapshotsDir)));
+
+  // Snapshot taken for minor update
+  std::vector<uint32_t> last_minor_version_components;
+  for (const auto& component : version_info::GetVersion().components()) {
+    // Decrement all but the major version.
+    last_minor_version_components.push_back(
+        !last_minor_version_components.empty() && component > 0 ? component - 1
+                                                                : component);
+  }
+  auto last_minor_version =
+      base::Version(last_minor_version_components).GetString();
+  base::WriteFile(user_data_dir.Append(kDowngradeLastVersionFile),
+                  last_minor_version.c_str(), last_minor_version.size());
+
+  EXPECT_FALSE(downgrade_manager.PrepareUserDataDirectoryForCurrentVersion(
+      user_data_dir));
+  EXPECT_FALSE(
+      base::PathExists(user_data_dir.Append(downgrade::kSnapshotsDir)));
+}
+
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+// Tests that Google Chrome canary takes snapshots on mid-milestone updates.
+IN_PROC_BROWSER_TEST_F(InProcessBrowserTest, CanarySameMilestoneSnapshot) {
+  DowngradeManager::EnableSnapshotsForTesting(true);
+  install_static::ScopedInstallDetails install_details(
+      /*system_level=*/false, install_static::CANARY_INDEX);
+  base::ScopedAllowBlockingForTesting scoped_allow_blocking;
+  base::FilePath user_data_dir;
+  ASSERT_TRUE(base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
+  auto current_version = version_info::GetVersion().GetString();
+
+  downgrade::DowngradeManager downgrade_manager;
+
+  // No snapshots for same version.
+  base::WriteFile(user_data_dir.Append(kDowngradeLastVersionFile),
+                  current_version.c_str(), current_version.size());
+  EXPECT_FALSE(downgrade_manager.PrepareUserDataDirectoryForCurrentVersion(
+      user_data_dir));
+  EXPECT_FALSE(
+      base::PathExists(user_data_dir.Append(downgrade::kSnapshotsDir)));
+
+  // Snapshot taken for minor update
+  std::vector<uint32_t> last_minor_version_components;
+  for (const auto& component : version_info::GetVersion().components()) {
+    // Decrement all but the major version.
+    last_minor_version_components.push_back(
+        !last_minor_version_components.empty() && component > 0 ? component - 1
+                                                                : component);
+  }
+  auto last_minor_version =
+      base::Version(last_minor_version_components).GetString();
+  base::WriteFile(user_data_dir.Append(kDowngradeLastVersionFile),
+                  last_minor_version.c_str(), last_minor_version.size());
+
+  EXPECT_FALSE(downgrade_manager.PrepareUserDataDirectoryForCurrentVersion(
+      user_data_dir));
+  EXPECT_TRUE(base::PathExists(user_data_dir.Append(downgrade::kSnapshotsDir)));
+}
+#endif
+
 }  // namespace downgrade
diff --git a/chrome/browser/extensions/api/identity/identity_api.cc b/chrome/browser/extensions/api/identity/identity_api.cc
index 7c83df99..b445f29 100644
--- a/chrome/browser/extensions/api/identity/identity_api.cc
+++ b/chrome/browser/extensions/api/identity/identity_api.cc
@@ -260,7 +260,8 @@
 }
 
 bool IdentityAPI::AreExtensionsRestrictedToPrimaryAccount() {
-  return !AccountConsistencyModeManager::IsDiceEnabledForProfile(profile_);
+  return !AccountConsistencyModeManager::IsDiceEnabledForProfile(profile_) &&
+         !AccountConsistencyModeManager::IsMirrorEnabledForProfile(profile_);
 }
 
 IdentityAPI::IdentityAPI(Profile* profile,
diff --git a/chrome/browser/extensions/api/identity/identity_api_unittest.cc b/chrome/browser/extensions/api/identity/identity_api_unittest.cc
index 61fd982..fb8e51b 100644
--- a/chrome/browser/extensions/api/identity/identity_api_unittest.cc
+++ b/chrome/browser/extensions/api/identity/identity_api_unittest.cc
@@ -9,7 +9,6 @@
 #include "base/optional.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "build/build_config.h"
 #include "chrome/browser/extensions/test_extension_prefs.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/signin/public/base/signin_buildflags.h"
@@ -59,16 +58,10 @@
   std::unique_ptr<IdentityAPI> api_;
 };
 
-#if BUILDFLAG(ENABLE_DICE_SUPPORT)
-// Tests that all accounts in extensions is enabled when Dice is enabled.
-TEST_F(IdentityAPITest, DiceAllAccountsExtensions) {
+// Tests that all accounts in extensions is enabled for regular profiles.
+TEST_F(IdentityAPITest, AllAccountsExtensionEnabled) {
   EXPECT_FALSE(api()->AreExtensionsRestrictedToPrimaryAccount());
 }
-#else
-TEST_F(IdentityAPITest, AllAccountsExtensionDisabled) {
-  EXPECT_TRUE(api()->AreExtensionsRestrictedToPrimaryAccount());
-}
-#endif
 
 TEST_F(IdentityAPITest, GetGaiaIdForExtension) {
   std::string extension_id = prefs()->AddExtensionAndReturnId("extension");
diff --git a/chrome/browser/extensions/api/identity/identity_apitest.cc b/chrome/browser/extensions/api/identity/identity_apitest.cc
index d2e7053..ee7799c 100644
--- a/chrome/browser/extensions/api/identity/identity_apitest.cc
+++ b/chrome/browser/extensions/api/identity/identity_apitest.cc
@@ -69,7 +69,11 @@
 
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/chromeos/login/users/mock_user_manager.h"
+#include "chrome/browser/chromeos/net/network_portal_detector_test_impl.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
+#include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
 #include "chromeos/tpm/stub_install_attributes.h"
 #include "components/user_manager/scoped_user_manager.h"
 #endif
@@ -88,6 +92,27 @@
 static const char kAccessToken[] = "auth_token";
 static const char kExtensionId[] = "ext_id";
 
+#if defined(OS_CHROMEOS)
+void InitNetwork() {
+  const chromeos::NetworkState* default_network =
+      chromeos::NetworkHandler::Get()
+          ->network_state_handler()
+          ->DefaultNetwork();
+
+  auto* portal_detector = new chromeos::NetworkPortalDetectorTestImpl();
+  portal_detector->SetDefaultNetworkForTesting(default_network->guid());
+
+  chromeos::NetworkPortalDetector::CaptivePortalState online_state;
+  online_state.status =
+      chromeos::NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE;
+  online_state.response_code = 204;
+  portal_detector->SetDetectionResultsForTesting(default_network->guid(),
+                                                 online_state);
+
+  chromeos::network_portal_detector::InitializeForTesting(portal_detector);
+}
+#endif
+
 // Asynchronous function runner allows tests to manipulate the browser window
 // after the call happens.
 class AsyncFunctionRunner {
@@ -499,6 +524,11 @@
   void SetUpOnMainThread() override {
     AsyncExtensionBrowserTest::SetUpOnMainThread();
 
+#if defined(OS_CHROMEOS)
+    // Fake the network online state so that Gaia requests can come through.
+    InitNetwork();
+#endif
+
     identity_test_env_profile_adaptor_ =
         std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile());
 
@@ -610,11 +640,7 @@
 };
 
 IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, AllAccountsOn) {
-#if BUILDFLAG(ENABLE_DICE_SUPPORT)
   EXPECT_FALSE(id_api()->AreExtensionsRestrictedToPrimaryAccount());
-#else
-  EXPECT_TRUE(id_api()->AreExtensionsRestrictedToPrimaryAccount());
-#endif
 }
 
 IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, NoneSignedIn) {
@@ -2020,6 +2046,8 @@
   }
 }
 
+// The signin flow is simply not used on ChromeOS.
+#if !defined(OS_CHROMEOS)
 IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
                        MultiSecondaryInteractiveInvalidToken) {
   // Setup a secondary account with no valid refresh token, and try to get a
@@ -2059,6 +2087,7 @@
     EXPECT_TRUE(func->scope_ui_shown());
   }
 }
+#endif
 
 IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ScopesDefault) {
   SignIn("primary@example.com");
diff --git a/chrome/browser/extensions/api/tabs/tabs_test.cc b/chrome/browser/extensions/api/tabs/tabs_test.cc
index 0e27b87..678e972 100644
--- a/chrome/browser/extensions/api/tabs/tabs_test.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_test.cc
@@ -343,8 +343,13 @@
   CloseAppWindow(app_window);
 }
 
-// Flaky. https://crbug.com/1035620
-IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DISABLED_GetAllWindowsAllTypes) {
+// Flaky on Windows. https://crbug.com/1035620
+#if defined(OS_WIN)
+#define MAYBE_GetAllWindowsAllTypes DISABLED_GetAllWindowsAllTypes
+#else
+#define MAYBE_GetAllWindowsAllTypes GetAllWindowsAllTypes
+#endif
+IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, MAYBE_GetAllWindowsAllTypes) {
   const size_t NUM_WINDOWS = 5;
   std::set<int> window_ids;
   std::set<int> result_ids;
diff --git a/chrome/browser/extensions/context_menu_matcher.cc b/chrome/browser/extensions/context_menu_matcher.cc
index 4989ad4..6dc75c2 100644
--- a/chrome/browser/extensions/context_menu_matcher.cc
+++ b/chrome/browser/extensions/context_menu_matcher.cc
@@ -14,6 +14,7 @@
 #include "content/public/browser/context_menu_params.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/favicon_size.h"
 #include "ui/gfx/image/image.h"
 
@@ -355,7 +356,7 @@
   gfx::Image icon = menu_manager->GetIconForExtension(extension_id);
   DCHECK_EQ(gfx::kFaviconSize, icon.Width());
   DCHECK_EQ(gfx::kFaviconSize, icon.Height());
-  menu_model_->SetIcon(index, icon);
+  menu_model_->SetIcon(index, ui::ImageModel::FromImage(icon));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/extension_context_menu_model.cc b/chrome/browser/extensions/extension_context_menu_model.cc
index 813e77b..7aad694 100644
--- a/chrome/browser/extensions/extension_context_menu_model.cc
+++ b/chrome/browser/extensions/extension_context_menu_model.cc
@@ -45,6 +45,7 @@
 #include "extensions/common/manifest_handlers/options_page_info.h"
 #include "extensions/common/manifest_url_handlers.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/models/menu_separator_types.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/color_palette.h"
@@ -411,9 +412,10 @@
     AddItem(UNINSTALL, l10n_util::GetStringUTF16(message_id));
     if (is_required_by_policy) {
       int uninstall_index = GetIndexOfCommandId(UNINSTALL);
+      // TODO (kylixrd): Investigate the usage of the hard-coded color.
       SetIcon(uninstall_index,
-              gfx::Image(gfx::CreateVectorIcon(vector_icons::kBusinessIcon, 16,
-                                               gfx::kChromeIconGrey)));
+              ui::ImageModel::FromVectorIcon(vector_icons::kBusinessIcon,
+                                             gfx::kChromeIconGrey, 16));
     }
   }
 
diff --git a/chrome/browser/extensions/extension_context_menu_model_unittest.cc b/chrome/browser/extensions/extension_context_menu_model_unittest.cc
index a8a9ce6..c3b2b49 100644
--- a/chrome/browser/extensions/extension_context_menu_model_unittest.cc
+++ b/chrome/browser/extensions/extension_context_menu_model_unittest.cc
@@ -402,8 +402,7 @@
       menu.GetIndexOfCommandId(ExtensionContextMenuModel::UNINSTALL);
   // There should also be an icon to visually indicate why uninstallation is
   // forbidden.
-  gfx::Image icon;
-  EXPECT_TRUE(menu.GetIconAt(uninstall_index, &icon));
+  ui::ImageModel icon = menu.GetIconAt(uninstall_index);
   EXPECT_FALSE(icon.IsEmpty());
 
   // Don't leave |policy_provider| dangling.
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 677360d1..b10b02f9 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2647,6 +2647,11 @@
     "expiry_milestone": 82
   },
   {
+    "name": "installed-apps-in-cbd",
+    "owners": [ "jarrydg", "pwnall" ],
+    "expiry_milestone": 89
+  },
+  {
     "name": "instant-tethering",
     "owners": [ "hansberry", "khorimoto", "vecore", "cros-system-services@google.com" ],
     // Though Instant Tethering is now a launched feature, it is only marked enabled on select,
@@ -2743,6 +2748,11 @@
     "expiry_milestone": 88
   },
   {
+    "name": "managed-bookmarks-ios",
+    "owners": [ "edchin", "rohitrao" ],
+    "expiry_milestone": 90
+  },
+  {
     "name": "media-app",
     "owners": [ "//chromeos/components/media_app_ui/OWNERS" ],
     "expiry_milestone": 90
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 25cec00..c25a8d9 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1100,6 +1100,11 @@
 const char kInProductHelpDemoModeChoiceDescription[] =
     "Selects the In-Product Help demo mode.";
 
+const char kInstalledAppsInCbdName[] = "Installed Apps in Clear Browsing Data";
+const char kInstalledAppsInCbdDescription[] =
+    "Adds the installed apps warning dialog to the clear browsing data flow "
+    "which allows users to protect installed apps' data from being deleted.";
+
 const char kJavascriptHarmonyName[] = "Experimental JavaScript";
 const char kJavascriptHarmonyDescription[] =
     "Enable web pages to use experimental JavaScript features.";
@@ -1832,7 +1837,7 @@
     "Split partially occluded quads";
 const char kSplitPartiallyOccludedQuadsDescription[] =
     "Split partially occluded quads before drawing, discarding the occluded "
-    "regions, in order to redruce compositing overdraw.";
+    "regions, in order to reduce compositing overdraw.";
 
 const char kStrictOriginIsolationName[] = "Strict-Origin-Isolation";
 const char kStrictOriginIsolationDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index dff2339..b6669c3 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -654,6 +654,9 @@
 extern const char kInProductHelpDemoModeChoiceName[];
 extern const char kInProductHelpDemoModeChoiceDescription[];
 
+extern const char kInstalledAppsInCbdName[];
+extern const char kInstalledAppsInCbdDescription[];
+
 extern const char kJavascriptHarmonyName[];
 extern const char kJavascriptHarmonyDescription[];
 
diff --git a/chrome/browser/metrics/chrome_metrics_services_manager_client.h b/chrome/browser/metrics/chrome_metrics_services_manager_client.h
index 9a5a325..1d0614f 100644
--- a/chrome/browser/metrics/chrome_metrics_services_manager_client.h
+++ b/chrome/browser/metrics/chrome_metrics_services_manager_client.h
@@ -24,6 +24,7 @@
 
 // Used only for testing.
 namespace internal {
+// TODO(crbug.com/1068796): Replace kMetricsReportingFeature with a better name.
 extern const base::Feature kMetricsReportingFeature;
 }
 }
diff --git a/chrome/browser/metrics/ukm_browsertest.cc b/chrome/browser/metrics/ukm_browsertest.cc
index 49977de..396a681 100644
--- a/chrome/browser/metrics/ukm_browsertest.cc
+++ b/chrome/browser/metrics/ukm_browsertest.cc
@@ -3,10 +3,12 @@
 // found in the LICENSE file.
 
 #include <memory>
+#include <string>
 
 #include "base/bind.h"
+#include "base/files/file_path.h"
 #include "base/run_loop.h"
-#include "base/stl_util.h"
+#include "base/strings/string16.h"
 #include "base/strings/string_util.h"
 #include "base/system/sys_info.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -45,6 +47,7 @@
 #include "components/sync/test/fake_server/fake_server_network_resources.h"
 #include "components/ukm/content/source_url_recorder.h"
 #include "components/ukm/ukm_service.h"
+#include "components/ukm/ukm_test_helper.h"
 #include "components/unified_consent/unified_consent_service.h"
 #include "components/variations/service/variations_field_trial_creator.h"
 #include "components/version_info/version_info.h"
@@ -81,6 +84,10 @@
   content::RunAllTasksUntilIdle();
 }
 
+ukm::UkmService* GetUkmService() {
+  return g_browser_process->GetMetricsServicesManager()->GetUkmService();
+}
+
 }  // namespace
 
 // An observer that returns back to test code after a new profile is
@@ -147,93 +154,23 @@
 class UkmBrowserTestBase : public SyncTest {
  public:
   UkmBrowserTestBase() : SyncTest(SINGLE_CLIENT) {
-    // Explicitly enable UKM and disable the MetricsReporting (which should
-    // not affect UKM).
+    // TODO(crbug.com/1068796): Replace kMetricsReportingFeature with a more
+    // apt name.
+    // Explicitly enable UKM and disable metrics reporting. Disabling metrics
+    // reporting should affect only UMA--not UKM.
     scoped_feature_list_.InitWithFeatures({ukm::kUkmFeature},
                                           {internal::kMetricsReportingFeature});
   }
 
-  bool ukm_enabled() const {
-    auto* service = ukm_service();
-    return service ? service->recording_enabled_ : false;
-  }
-  bool ukm_extensions_enabled() const {
-    auto* service = ukm_service();
-    return service ? service->extensions_enabled_ : false;
-  }
-  uint64_t client_id() const {
-    auto* service = ukm_service();
-    DCHECK(service);
-    // Can be non-zero only if UpdateUploadPermissions(true) has been called.
-    return service->client_id_;
-  }
-  ukm::UkmSource* GetSource(ukm::SourceId source_id) {
-    auto* service = ukm_service();
-    if (!service)
-      return nullptr;
-    auto it = service->sources().find(source_id);
-    return it == service->sources().end() ? nullptr : it->second.get();
-  }
-  ukm::UkmSource* NavigateAndGetSource(Browser* browser, const GURL& url) {
+  ukm::UkmSource* NavigateAndGetSource(const GURL& url,
+                                       Browser* browser,
+                                       ukm::UkmTestHelper* ukm_test_helper) {
     content::NavigationHandleObserver observer(
         browser->tab_strip_model()->GetActiveWebContents(), url);
     ui_test_utils::NavigateToURL(browser, url);
     const ukm::SourceId source_id = ukm::ConvertToSourceId(
         observer.navigation_id(), ukm::SourceIdType::NAVIGATION_ID);
-    return GetSource(source_id);
-  }
-  bool HasSource(ukm::SourceId source_id) const {
-    auto* service = ukm_service();
-    return service && base::Contains(service->sources(), source_id);
-  }
-  void RecordDummySource(ukm::SourceId source_id) {
-    auto* service = ukm_service();
-    if (service)
-      service->UpdateSourceURL(source_id, GURL("http://example.com"));
-  }
-  bool IsSourceMarkedAsObsolete(ukm::SourceId source_id) {
-    auto* service = ukm_service();
-    DCHECK(service);
-    return base::Contains(service->recordings_.obsolete_source_ids, source_id);
-  }
-  void BuildAndStoreUkmLog() {
-    auto* service = ukm_service();
-    DCHECK(service);
-    // Wait for initialization to complete before flushing.
-    base::RunLoop run_loop;
-    service->SetInitializationCompleteCallbackForTesting(run_loop.QuitClosure());
-    run_loop.Run();
-    DCHECK(service->initialize_complete_);
-
-    service->Flush();
-    DCHECK(service->reporting_service_.ukm_log_store()->has_unsent_logs());
-  }
-  bool HasUnsentUkmLogs() {
-    auto* service = ukm_service();
-    DCHECK(service);
-    return service->reporting_service_.ukm_log_store()->has_unsent_logs();
-  }
-
-  ukm::Report GetUkmReport() {
-    EXPECT_TRUE(HasUnsentUkmLogs());
-
-    UnsentLogStore* log_store =
-        ukm_service()->reporting_service_.ukm_log_store();
-    if (log_store->has_staged_log()) {
-      // For testing purposes, we are examining the content of a staged log
-      // without ever sending the log, so discard any previously staged log.
-      log_store->DiscardStagedLog();
-    }
-    log_store->StageNextLog();
-    EXPECT_TRUE(log_store->has_staged_log());
-
-    std::string uncompressed_log_data;
-    EXPECT_TRUE(compression::GzipUncompress(log_store->staged_log(),
-                                            &uncompressed_log_data));
-
-    ukm::Report report;
-    EXPECT_TRUE(report.ParseFromString(uncompressed_log_data));
-    return report;
+    return ukm_test_helper->GetSource(source_id);
   }
 
  protected:
@@ -270,10 +207,6 @@
   }
 
  private:
-  ukm::UkmService* ukm_service() const {
-    return g_browser_process->GetMetricsServicesManager()->GetUkmService();
-  }
-
   base::test::ScopedFeatureList scoped_feature_list_;
 
   DISALLOW_COPY_AND_ASSIGN(UkmBrowserTestBase);
@@ -345,27 +278,29 @@
 
  private:
   base::FilePath local_state_path_;
+
   DISALLOW_COPY_AND_ASSIGN(UkmConsentParamBrowserTest);
 };
 
 class UkmEnabledChecker : public SingleClientStatusChangeChecker {
  public:
-  UkmEnabledChecker(UkmBrowserTestBase* test,
-                    syncer::ProfileSyncService* service,
+  UkmEnabledChecker(syncer::ProfileSyncService* service,
+                    ukm::UkmTestHelper* ukm_test_helper,
                     bool want_enabled)
       : SingleClientStatusChangeChecker(service),
-        test_(test),
+        ukm_test_helper_(ukm_test_helper),
         want_enabled_(want_enabled) {}
 
   // StatusChangeChecker:
   bool IsExitConditionSatisfied(std::ostream* os) override {
-    *os << "Waiting for ukm_enabled=" << (want_enabled_ ? "true" : "false");
-    return test_->ukm_enabled() == want_enabled_;
+    *os << "Waiting for IsUkmEnabled=" << (want_enabled_ ? "true" : "false");
+    return ukm_test_helper_->IsRecordingEnabled() == want_enabled_;
   }
 
  private:
-  UkmBrowserTestBase* const test_;
+  ukm::UkmTestHelper* const ukm_test_helper_;
   const bool want_enabled_;
+
   DISALLOW_COPY_AND_ASSIGN(UkmEnabledChecker);
 };
 
@@ -404,6 +339,7 @@
 // chrome/android/javatests/src/org/chromium/chrome/browser/metrics/
 // UkmTest.java.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, RegularPlusIncognitoCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -411,28 +347,28 @@
       EnableSyncForProfile(profile);
 
   Browser* sync_browser = CreateBrowser(profile);
-  EXPECT_TRUE(ukm_enabled());
-  uint64_t original_client_id = client_id();
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  uint64_t original_client_id = ukm_test_helper.GetClientId();
   EXPECT_NE(0U, original_client_id);
 
   Browser* incognito_browser = CreateIncognitoBrowser();
-  EXPECT_FALSE(ukm_enabled());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
 
   // Opening another regular browser mustn't enable UKM.
   Browser* regular_browser = CreateBrowser(profile);
-  EXPECT_FALSE(ukm_enabled());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
 
   // Opening and closing another Incognito browser mustn't enable UKM.
   CloseBrowserSynchronously(CreateIncognitoBrowser());
-  EXPECT_FALSE(ukm_enabled());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
 
   CloseBrowserSynchronously(regular_browser);
-  EXPECT_FALSE(ukm_enabled());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
 
   CloseBrowserSynchronously(incognito_browser);
-  EXPECT_TRUE(ukm_enabled());
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
   // Client ID should not have been reset.
-  EXPECT_EQ(original_client_id, client_id());
+  EXPECT_EQ(original_client_id, ukm_test_helper.GetClientId());
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(sync_browser);
@@ -443,6 +379,7 @@
 // chrome/android/javatests/src/org/chromium/chrome/browser/metrics/
 // UkmTest.java.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, IncognitoPlusRegularCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -450,13 +387,13 @@
       EnableSyncForProfile(profile);
 
   Browser* incognito_browser = CreateIncognitoBrowser();
-  EXPECT_FALSE(ukm_enabled());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
 
   Browser* sync_browser = CreateBrowser(profile);
-  EXPECT_FALSE(ukm_enabled());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
 
   CloseBrowserSynchronously(incognito_browser);
-  EXPECT_TRUE(ukm_enabled());
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(sync_browser);
@@ -464,6 +401,7 @@
 
 // Make sure that UKM is disabled while a guest profile's window is open.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, RegularPlusGuestCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -471,20 +409,20 @@
       EnableSyncForProfile(profile);
 
   Browser* regular_browser = CreateBrowser(profile);
-  EXPECT_TRUE(ukm_enabled());
-  uint64_t original_client_id = client_id();
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  uint64_t original_client_id = ukm_test_helper.GetClientId();
 
   // Create browser for guest profile. Only "off the record" browsers may be
   // opened in this mode.
   Profile* guest_profile = CreateGuestProfile();
   Browser* guest_browser = CreateIncognitoBrowser(guest_profile);
-  EXPECT_FALSE(ukm_enabled());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
 
   CloseBrowserSynchronously(guest_browser);
   // TODO(crbug/746076): UKM doesn't actually get re-enabled yet.
-  // EXPECT_TRUE(ukm_enabled());
+  // EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
   // Client ID should not have been reset.
-  EXPECT_EQ(original_client_id, client_id());
+  EXPECT_EQ(original_client_id, ukm_test_helper.GetClientId());
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(regular_browser);
@@ -492,6 +430,7 @@
 
 // Make sure that UKM is disabled while an non-sync profile's window is open.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, OpenNonSyncCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -499,19 +438,19 @@
       EnableSyncForProfile(profile);
 
   Browser* sync_browser = CreateBrowser(profile);
-  EXPECT_TRUE(ukm_enabled());
-  uint64_t original_client_id = client_id();
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  uint64_t original_client_id = ukm_test_helper.GetClientId();
   EXPECT_NE(0U, original_client_id);
 
   Profile* nonsync_profile = CreateNonSyncProfile();
   Browser* nonsync_browser = CreateBrowser(nonsync_profile);
-  EXPECT_FALSE(ukm_enabled());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
 
   CloseBrowserSynchronously(nonsync_browser);
   // TODO(crbug/746076): UKM doesn't actually get re-enabled yet.
-  // EXPECT_TRUE(ukm_enabled());
+  // EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
   // Client ID should not have been reset.
-  EXPECT_EQ(original_client_id, client_id());
+  EXPECT_EQ(original_client_id, ukm_test_helper.GetClientId());
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(sync_browser);
@@ -522,6 +461,7 @@
 // chrome/android/javatests/src/org/chromium/chrome/browser/sync/
 // UkmTest.java.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, MetricsConsentCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -529,29 +469,30 @@
       EnableSyncForProfile(profile);
 
   Browser* sync_browser = CreateBrowser(profile);
-  EXPECT_TRUE(ukm_enabled());
-  uint64_t original_client_id = client_id();
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  uint64_t original_client_id = ukm_test_helper.GetClientId();
   EXPECT_NE(0U, original_client_id);
 
   // Make sure there is a persistent log.
-  BuildAndStoreUkmLog();
-  EXPECT_TRUE(HasUnsentUkmLogs());
+  ukm_test_helper.BuildAndStoreLog();
+  EXPECT_TRUE(ukm_test_helper.HasUnsentLogs());
 
   metrics_consent.Update(false);
-  EXPECT_FALSE(ukm_enabled());
-  EXPECT_FALSE(HasUnsentUkmLogs());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_FALSE(ukm_test_helper.HasUnsentLogs());
 
   metrics_consent.Update(true);
 
-  EXPECT_TRUE(ukm_enabled());
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
   // Client ID should have been reset.
-  EXPECT_NE(original_client_id, client_id());
+  EXPECT_NE(original_client_id, ukm_test_helper.GetClientId());
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(sync_browser);
 }
 
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, LogProtoData) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -559,30 +500,30 @@
       EnableSyncForProfile(profile);
 
   Browser* sync_browser = CreateBrowser(profile);
-  EXPECT_TRUE(ukm_enabled());
-  uint64_t original_client_id = client_id();
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  uint64_t original_client_id = ukm_test_helper.GetClientId();
   EXPECT_NE(0U, original_client_id);
 
   // Make sure there is a persistent log.
-  BuildAndStoreUkmLog();
-  EXPECT_TRUE(HasUnsentUkmLogs());
+  ukm_test_helper.BuildAndStoreLog();
+  EXPECT_TRUE(ukm_test_helper.HasUnsentLogs());
 
   // Check log contents.
-  ukm::Report report = GetUkmReport();
-  EXPECT_EQ(original_client_id, report.client_id());
+  std::unique_ptr<ukm::Report> report = ukm_test_helper.GetUkmReport();
+  EXPECT_EQ(original_client_id, report->client_id());
   // Note: The version number reported in the proto may have a suffix, such as
   // "-64-devel", so use use StartsWith() rather than checking for equality.
-  EXPECT_TRUE(base::StartsWith(report.system_profile().app_version(),
+  EXPECT_TRUE(base::StartsWith(report->system_profile().app_version(),
                                version_info::GetVersionNumber(),
                                base::CompareCase::SENSITIVE));
 
 // Chrome OS hardware class comes from a different API than on other platforms.
 #if defined(OS_CHROMEOS)
   EXPECT_EQ(variations::VariationsFieldTrialCreator::GetShortHardwareClass(),
-            report.system_profile().hardware().hardware_class());
+            report->system_profile().hardware().hardware_class());
 #else   // !defined(OS_CHROMEOS)
   EXPECT_EQ(base::SysInfo::HardwareModelName(),
-            report.system_profile().hardware().hardware_class());
+            report->system_profile().hardware().hardware_class());
 #endif  // defined(OS_CHROMEOS)
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
@@ -592,6 +533,7 @@
 // TODO(crbug/1016118): Add the remaining test cases.
 IN_PROC_BROWSER_TEST_P(UkmBrowserTestWithDemographics,
                        AddSyncedUserBirthYearAndGenderToProtoData) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   test::DemographicsTestParams param = GetParam();
   MetricsConsentOverride metrics_consent(true);
 
@@ -616,24 +558,24 @@
   ASSERT_EQ(1, num_clients());
 
   Browser* sync_browser = CreateBrowser(test_profile);
-  EXPECT_TRUE(ukm_enabled());
-  uint64_t original_client_id = client_id();
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  uint64_t original_client_id = ukm_test_helper.GetClientId();
   EXPECT_NE(0U, original_client_id);
 
   // Log UKM metrics report.
-  BuildAndStoreUkmLog();
-  EXPECT_TRUE(HasUnsentUkmLogs());
+  ukm_test_helper.BuildAndStoreLog();
+  EXPECT_TRUE(ukm_test_helper.HasUnsentLogs());
 
   // Check the log's content and the histogram.
-  ukm::Report report = GetUkmReport();
+  std::unique_ptr<ukm::Report> report = ukm_test_helper.GetUkmReport();
   if (param.expect_reported_demographics) {
     EXPECT_EQ(test::GetNoisedBirthYear(test_birth_year, *test_profile),
-              report.user_demographics().birth_year());
-    EXPECT_EQ(test_gender, report.user_demographics().gender());
+              report->user_demographics().birth_year());
+    EXPECT_EQ(test_gender, report->user_demographics().gender());
     histogram.ExpectUniqueSample("UKM.UserDemographics.Status",
                                  syncer::UserDemographicsStatus::kSuccess, 1);
   } else {
-    EXPECT_FALSE(report.has_user_demographics());
+    EXPECT_FALSE(report->has_user_demographics());
     histogram.ExpectTotalCount("UKM.UserDemographics.Status", /*count=*/0);
   }
 
@@ -662,6 +604,7 @@
 // Verifies that network provider attaches effective connection type correctly
 // to the UKM report.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, NetworkProviderPopulatesSystemProfile) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   // Override network quality to 2G. This should cause the
   // |max_effective_connection_type| in the system profile to be set to 2G.
   g_browser_process->network_quality_tracker()
@@ -675,8 +618,8 @@
       EnableSyncForProfile(profile);
 
   Browser* sync_browser = CreateBrowser(profile);
-  EXPECT_TRUE(ukm_enabled());
-  uint64_t original_client_id = client_id();
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  uint64_t original_client_id = ukm_test_helper.GetClientId();
   EXPECT_NE(0U, original_client_id);
 
   // Override network quality to 4G. This should cause the
@@ -686,15 +629,15 @@
           net::EFFECTIVE_CONNECTION_TYPE_4G);
 
   // Make sure there is a persistent log.
-  BuildAndStoreUkmLog();
-  EXPECT_TRUE(HasUnsentUkmLogs());
+  ukm_test_helper.BuildAndStoreLog();
+  EXPECT_TRUE(ukm_test_helper.HasUnsentLogs());
   // Check log contents.
-  ukm::Report report = GetUkmReport();
+  std::unique_ptr<ukm::Report> report = ukm_test_helper.GetUkmReport();
 
   EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G,
-            report.system_profile().network().min_effective_connection_type());
+            report->system_profile().network().min_effective_connection_type());
   EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_4G,
-            report.system_profile().network().max_effective_connection_type());
+            report->system_profile().network().max_effective_connection_type());
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(sync_browser);
@@ -705,19 +648,20 @@
 // chrome/android/javatests/src/org/chromium/chrome/browser/sync/
 // UkmTest.java.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, ConsentAddedButNoSyncCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(false);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
   Browser* browser = CreateBrowser(profile);
-  EXPECT_FALSE(ukm_enabled());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
 
   metrics_consent.Update(true);
-  EXPECT_FALSE(ukm_enabled());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
 
   std::unique_ptr<ProfileSyncServiceHarness> harness =
       EnableSyncForProfile(profile);
   g_browser_process->GetMetricsServicesManager()->UpdateUploadPermissions(true);
-  EXPECT_TRUE(ukm_enabled());
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(browser);
@@ -726,6 +670,7 @@
 // Make sure that extension URLs are disabled when an open sync window
 // disables it.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, SingleDisableExtensionsSyncCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -733,22 +678,22 @@
       EnableSyncForProfile(profile);
 
   Browser* sync_browser = CreateBrowser(profile);
-  EXPECT_TRUE(ukm_enabled());
-  EXPECT_TRUE(ukm_extensions_enabled());
-  uint64_t original_client_id = client_id();
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_TRUE(ukm_test_helper.IsExtensionRecordingEnabled());
+  uint64_t original_client_id = ukm_test_helper.GetClientId();
   EXPECT_NE(0U, original_client_id);
 
   ASSERT_TRUE(
       harness->DisableSyncForType(syncer::UserSelectableType::kExtensions));
-  EXPECT_TRUE(ukm_enabled());
-  EXPECT_FALSE(ukm_extensions_enabled());
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_FALSE(ukm_test_helper.IsExtensionRecordingEnabled());
 
   ASSERT_TRUE(
       harness->EnableSyncForType(syncer::UserSelectableType::kExtensions));
-  EXPECT_TRUE(ukm_enabled());
-  EXPECT_TRUE(ukm_extensions_enabled());
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_TRUE(ukm_test_helper.IsExtensionRecordingEnabled());
   // Client ID should not be reset.
-  EXPECT_EQ(original_client_id, client_id());
+  EXPECT_EQ(original_client_id, ukm_test_helper.GetClientId());
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(sync_browser);
@@ -757,6 +702,7 @@
 // Make sure that extension URLs are disabled when any open sync window
 // disables it.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, MultiDisableExtensionsSyncCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile1 = ProfileManager::GetActiveUserProfile();
@@ -764,26 +710,26 @@
       EnableSyncForProfile(profile1);
 
   Browser* browser1 = CreateBrowser(profile1);
-  EXPECT_TRUE(ukm_enabled());
-  uint64_t original_client_id = client_id();
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  uint64_t original_client_id = ukm_test_helper.GetClientId();
   EXPECT_NE(0U, original_client_id);
 
   Profile* profile2 = CreateNonSyncProfile();
   std::unique_ptr<ProfileSyncServiceHarness> harness2 =
       EnableSyncForProfile(profile2);
   Browser* browser2 = CreateBrowser(profile2);
-  EXPECT_TRUE(ukm_enabled());
-  EXPECT_TRUE(ukm_extensions_enabled());
-  EXPECT_EQ(original_client_id, client_id());
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_TRUE(ukm_test_helper.IsExtensionRecordingEnabled());
+  EXPECT_EQ(original_client_id, ukm_test_helper.GetClientId());
 
   harness2->DisableSyncForType(syncer::UserSelectableType::kExtensions);
-  EXPECT_TRUE(ukm_enabled());
-  EXPECT_FALSE(ukm_extensions_enabled());
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_FALSE(ukm_test_helper.IsExtensionRecordingEnabled());
 
   harness2->EnableSyncForType(syncer::UserSelectableType::kExtensions);
-  EXPECT_TRUE(ukm_enabled());
-  EXPECT_TRUE(ukm_extensions_enabled());
-  EXPECT_EQ(original_client_id, client_id());
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_TRUE(ukm_test_helper.IsExtensionRecordingEnabled());
+  EXPECT_EQ(original_client_id, ukm_test_helper.GetClientId());
 
   harness2->service()->GetUserSettings()->SetSyncRequested(false);
   harness1->service()->GetUserSettings()->SetSyncRequested(false);
@@ -792,6 +738,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, LogsTabId) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   ASSERT_TRUE(embedded_test_server()->Start());
   MetricsConsentOverride metrics_consent(true);
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -799,27 +746,31 @@
       EnableSyncForProfile(profile);
   Browser* sync_browser = CreateBrowser(profile);
 
-  const ukm::UkmSource* first_source = NavigateAndGetSource(
-      sync_browser, embedded_test_server()->GetURL("/title1.html"));
+  const ukm::UkmSource* first_source =
+      NavigateAndGetSource(embedded_test_server()->GetURL("/title1.html"),
+                           sync_browser, &ukm_test_helper);
 
   // Tab ids are incremented starting from 1. Since we started a new sync
   // browser, this is the second tab.
   EXPECT_EQ(2, first_source->navigation_data().tab_id);
 
   // Ensure the tab id is constant in a single tab.
-  const ukm::UkmSource* second_source = NavigateAndGetSource(
-      sync_browser, embedded_test_server()->GetURL("/title2.html"));
+  const ukm::UkmSource* second_source =
+      NavigateAndGetSource(embedded_test_server()->GetURL("/title2.html"),
+                           sync_browser, &ukm_test_helper);
   EXPECT_EQ(first_source->navigation_data().tab_id,
             second_source->navigation_data().tab_id);
 
   // Add a new tab, it should get a new tab id.
   chrome::NewTab(sync_browser);
-  const ukm::UkmSource* third_source = NavigateAndGetSource(
-      sync_browser, embedded_test_server()->GetURL("/title3.html"));
+  const ukm::UkmSource* third_source =
+      NavigateAndGetSource(embedded_test_server()->GetURL("/title3.html"),
+                           sync_browser, &ukm_test_helper);
   EXPECT_EQ(3, third_source->navigation_data().tab_id);
 }
 
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, LogsPreviousSourceId) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   ASSERT_TRUE(embedded_test_server()->Start());
   MetricsConsentOverride metrics_consent(true);
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -827,11 +778,13 @@
       EnableSyncForProfile(profile);
   Browser* sync_browser = CreateBrowser(profile);
 
-  const ukm::UkmSource* first_source = NavigateAndGetSource(
-      sync_browser, embedded_test_server()->GetURL("/title1.html"));
+  const ukm::UkmSource* first_source =
+      NavigateAndGetSource(embedded_test_server()->GetURL("/title1.html"),
+                           sync_browser, &ukm_test_helper);
 
-  const ukm::UkmSource* second_source = NavigateAndGetSource(
-      sync_browser, embedded_test_server()->GetURL("/title2.html"));
+  const ukm::UkmSource* second_source =
+      NavigateAndGetSource(embedded_test_server()->GetURL("/title2.html"),
+                           sync_browser, &ukm_test_helper);
   EXPECT_EQ(first_source->id(),
             second_source->navigation_data().previous_source_id);
 
@@ -847,20 +800,22 @@
   EXPECT_NE(opener, sync_browser->tab_strip_model()->GetActiveWebContents());
   ukm::SourceId new_id = ukm::GetSourceIdForWebContentsDocument(
       sync_browser->tab_strip_model()->GetActiveWebContents());
-  ukm::UkmSource* new_tab_source = GetSource(new_id);
+  ukm::UkmSource* new_tab_source = ukm_test_helper.GetSource(new_id);
   EXPECT_NE(nullptr, new_tab_source);
   EXPECT_EQ(ukm::kInvalidSourceId,
             new_tab_source->navigation_data().previous_source_id);
 
   // Subsequent navigations within the tab should get a previous_source_id field
   // set.
-  const ukm::UkmSource* subsequent_source = NavigateAndGetSource(
-      sync_browser, embedded_test_server()->GetURL("/title3.html"));
+  const ukm::UkmSource* subsequent_source =
+      NavigateAndGetSource(embedded_test_server()->GetURL("/title3.html"),
+                           sync_browser, &ukm_test_helper);
   EXPECT_EQ(new_tab_source->id(),
             subsequent_source->navigation_data().previous_source_id);
 }
 
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, LogsOpenerSource) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   ASSERT_TRUE(embedded_test_server()->Start());
   MetricsConsentOverride metrics_consent(true);
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -868,8 +823,9 @@
       EnableSyncForProfile(profile);
   Browser* sync_browser = CreateBrowser(profile);
 
-  const ukm::UkmSource* first_source = NavigateAndGetSource(
-      sync_browser, embedded_test_server()->GetURL("/title1.html"));
+  const ukm::UkmSource* first_source =
+      NavigateAndGetSource(embedded_test_server()->GetURL("/title1.html"),
+                           sync_browser, &ukm_test_helper);
   // This tab was not opened by another tab, so it should not have an opener
   // id.
   EXPECT_EQ(ukm::kInvalidSourceId,
@@ -887,14 +843,15 @@
   EXPECT_NE(opener, sync_browser->tab_strip_model()->GetActiveWebContents());
   ukm::SourceId new_id = ukm::GetSourceIdForWebContentsDocument(
       sync_browser->tab_strip_model()->GetActiveWebContents());
-  ukm::UkmSource* new_tab_source = GetSource(new_id);
+  ukm::UkmSource* new_tab_source = ukm_test_helper.GetSource(new_id);
   EXPECT_NE(nullptr, new_tab_source);
   EXPECT_EQ(first_source->id(),
             new_tab_source->navigation_data().opener_source_id);
 
   // Subsequent navigations within the tab should not get an opener set.
-  const ukm::UkmSource* subsequent_source = NavigateAndGetSource(
-      sync_browser, embedded_test_server()->GetURL("/title3.html"));
+  const ukm::UkmSource* subsequent_source =
+      NavigateAndGetSource(embedded_test_server()->GetURL("/title3.html"),
+                           sync_browser, &ukm_test_helper);
   EXPECT_EQ(ukm::kInvalidSourceId,
             subsequent_source->navigation_data().opener_source_id);
 }
@@ -907,6 +864,7 @@
 // chrome/android/javatests/src/org/chromium/chrome/browser/sync/
 // UkmTest.java.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, SingleSyncSignoutCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -914,13 +872,13 @@
       EnableSyncForProfile(profile);
 
   Browser* sync_browser = CreateBrowser(profile);
-  EXPECT_TRUE(ukm_enabled());
-  uint64_t original_client_id = client_id();
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  uint64_t original_client_id = ukm_test_helper.GetClientId();
   EXPECT_NE(0U, original_client_id);
 
   harness->SignOutPrimaryAccount();
-  EXPECT_FALSE(ukm_enabled());
-  EXPECT_NE(original_client_id, client_id());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_NE(original_client_id, ukm_test_helper.GetClientId());
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(sync_browser);
@@ -932,6 +890,7 @@
 #if !defined(OS_CHROMEOS)
 // Make sure that UKM is disabled when any profile signs out of Sync.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, MultiSyncSignoutCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile1 = ProfileManager::GetActiveUserProfile();
@@ -939,20 +898,20 @@
       EnableSyncForProfile(profile1);
 
   Browser* browser1 = CreateBrowser(profile1);
-  EXPECT_TRUE(ukm_enabled());
-  uint64_t original_client_id = client_id();
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  uint64_t original_client_id = ukm_test_helper.GetClientId();
   EXPECT_NE(0U, original_client_id);
 
   Profile* profile2 = CreateNonSyncProfile();
   std::unique_ptr<ProfileSyncServiceHarness> harness2 =
       EnableSyncForProfile(profile2);
   Browser* browser2 = CreateBrowser(profile2);
-  EXPECT_TRUE(ukm_enabled());
-  EXPECT_EQ(original_client_id, client_id());
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_EQ(original_client_id, ukm_test_helper.GetClientId());
 
   harness2->SignOutPrimaryAccount();
-  EXPECT_FALSE(ukm_enabled());
-  EXPECT_NE(original_client_id, client_id());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
+  EXPECT_NE(original_client_id, ukm_test_helper.GetClientId());
 
   harness2->service()->GetUserSettings()->SetSyncRequested(false);
   harness1->service()->GetUserSettings()->SetSyncRequested(false);
@@ -964,6 +923,7 @@
 // Make sure that if history/sync services weren't available when we tried to
 // attach listeners, UKM is not enabled.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, ServiceListenerInitFailedCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
   ChromeMetricsServiceClient::SetNotificationListenerSetupFailedForTesting(
       true);
@@ -973,15 +933,15 @@
       EnableSyncForProfile(profile);
 
   Browser* sync_browser = CreateBrowser(profile);
-  EXPECT_FALSE(ukm_enabled());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(sync_browser);
 }
 
 // Make sure that UKM is not affected by MetricsReporting Feature (sampling).
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, MetricsReportingCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   // Need to set the Metrics Default to OPT_OUT to trigger MetricsReporting.
-  DCHECK(g_browser_process);
   PrefService* local_state = g_browser_process->local_state();
   ForceRecordMetricsReportingDefaultState(local_state,
                                           EnableMetricsDefault::OPT_OUT);
@@ -997,7 +957,7 @@
       EnableSyncForProfile(profile);
 
   Browser* sync_browser = CreateBrowser(profile);
-  EXPECT_TRUE(ukm_enabled());
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(sync_browser);
@@ -1008,6 +968,7 @@
 // chrome/android/javatests/src/org/chromium/chrome/browser/metrics/
 // UkmTest.java.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, HistoryDeleteCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -1015,21 +976,21 @@
       EnableSyncForProfile(profile);
 
   Browser* sync_browser = CreateBrowser(profile);
-  EXPECT_TRUE(ukm_enabled());
-  uint64_t original_client_id = client_id();
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
+  uint64_t original_client_id = ukm_test_helper.GetClientId();
   EXPECT_NE(0U, original_client_id);
 
   const ukm::SourceId kDummySourceId = 0x54321;
-  RecordDummySource(kDummySourceId);
-  EXPECT_TRUE(HasSource(kDummySourceId));
+  ukm_test_helper.RecordSourceForTesting(kDummySourceId);
+  EXPECT_TRUE(ukm_test_helper.HasSource(kDummySourceId));
 
   ClearBrowsingData(profile);
   // Other sources may already have been recorded since the data was cleared,
   // but the dummy source should be gone.
-  EXPECT_FALSE(HasSource(kDummySourceId));
+  EXPECT_FALSE(ukm_test_helper.HasSource(kDummySourceId));
   // Client ID should NOT be reset.
-  EXPECT_EQ(original_client_id, client_id());
-  EXPECT_TRUE(ukm_enabled());
+  EXPECT_EQ(original_client_id, ukm_test_helper.GetClientId());
+  EXPECT_TRUE(ukm_test_helper.IsRecordingEnabled());
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(sync_browser);
@@ -1040,6 +1001,7 @@
 #if !defined(OS_CHROMEOS)
 IN_PROC_BROWSER_TEST_F(UkmBrowserTestWithSyncTransport,
                        NotEnabledForSecondaryAccountSync) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
 
   // Signing in (without making the account Chrome's primary one or explicitly
@@ -1069,11 +1031,12 @@
   ASSERT_TRUE(sync_service->GetUserSettings()->GetSelectedTypes().Has(
       syncer::UserSelectableType::kHistory));
 
-  EXPECT_FALSE(ukm_enabled());
+  EXPECT_FALSE(ukm_test_helper.IsRecordingEnabled());
 }
 #endif  // !OS_CHROMEOS
 
 IN_PROC_BROWSER_TEST_P(UkmConsentParamBrowserTest, GroupPolicyConsentCheck) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   // Note we are not using the synthetic MetricsConsentOverride since we are
   // testing directly from prefs.
 
@@ -1089,7 +1052,7 @@
   bool is_enabled = is_metrics_reporting_enabled_initial_value();
   EXPECT_EQ(is_enabled,
             UkmConsentParamBrowserTest::IsMetricsAndCrashReportingEnabled());
-  EXPECT_EQ(is_enabled, ukm_enabled());
+  EXPECT_EQ(is_enabled, ukm_test_helper.IsRecordingEnabled());
 
   harness->service()->GetUserSettings()->SetSyncRequested(false);
   CloseBrowserSynchronously(sync_browser);
@@ -1104,6 +1067,7 @@
 // one reporting cycle after the web contents are destroyed when the tab is
 // closed or when the user navigated away in the same tab.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, EvictObsoleteSources) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
   Profile* profile = ProfileManager::GetActiveUserProfile();
   std::unique_ptr<ProfileSyncServiceHarness> harness =
@@ -1136,11 +1100,11 @@
                                       ukm::SourceIdType::NAVIGATION_ID);
 
   // The UKM report contains this newly-created source.
-  BuildAndStoreUkmLog();
-  ukm::Report report = GetUkmReport();
+  ukm_test_helper.BuildAndStoreLog();
+  std::unique_ptr<ukm::Report> report = ukm_test_helper.GetUkmReport();
   bool has_source_id1 = false;
   bool has_source_id2 = false;
-  for (const auto& s : report.sources()) {
+  for (const auto& s : report->sources()) {
     has_source_id1 |= s.id() == source_id1;
     has_source_id2 |= s.id() == source_id2;
   }
@@ -1159,11 +1123,11 @@
   // The next report should again contain source 1 because the tab is still
   // alive, and also source 2 associated to the new tab that has just been
   // opened.
-  BuildAndStoreUkmLog();
-  report = GetUkmReport();
+  ukm_test_helper.BuildAndStoreLog();
+  report = ukm_test_helper.GetUkmReport();
   has_source_id1 = false;
   has_source_id2 = false;
-  for (const auto& s : report.sources()) {
+  for (const auto& s : report->sources()) {
     has_source_id1 |= s.id() == source_id1;
     has_source_id2 |= s.id() == source_id2;
   }
@@ -1176,11 +1140,11 @@
   sync_browser->tab_strip_model()->CloseWebContentsAt(
       1, TabStripModel::CloseTypes::CLOSE_NONE);
 
-  BuildAndStoreUkmLog();
-  report = GetUkmReport();
+  ukm_test_helper.BuildAndStoreLog();
+  report = ukm_test_helper.GetUkmReport();
   has_source_id1 = false;
   has_source_id2 = false;
-  for (const auto& s : report.sources()) {
+  for (const auto& s : report->sources()) {
     has_source_id1 |= s.id() == source_id1;
     has_source_id2 |= s.id() == source_id2;
   }
@@ -1195,11 +1159,11 @@
   // for source 1. Source 1 is thus no longer included in future reports. This
   // report will still contain source 2 because we might have associated entries
   // since the last report.
-  BuildAndStoreUkmLog();
-  report = GetUkmReport();
+  ukm_test_helper.BuildAndStoreLog();
+  report = ukm_test_helper.GetUkmReport();
   has_source_id1 = false;
   has_source_id2 = false;
-  for (const auto& s : report.sources()) {
+  for (const auto& s : report->sources()) {
     has_source_id1 |= s.id() == source_id1;
     has_source_id2 |= s.id() == source_id2;
   }
@@ -1207,11 +1171,11 @@
   EXPECT_TRUE(has_source_id2);
 
   // Neither source 1 or source 2 is alive anymore.
-  BuildAndStoreUkmLog();
-  report = GetUkmReport();
+  ukm_test_helper.BuildAndStoreLog();
+  report = ukm_test_helper.GetUkmReport();
   has_source_id1 = false;
   has_source_id2 = false;
-  for (const auto& s : report.sources()) {
+  for (const auto& s : report->sources()) {
     has_source_id1 |= s.id() == source_id1;
     has_source_id2 |= s.id() == source_id2;
   }
@@ -1225,6 +1189,7 @@
 // navigation happens.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest,
                        MarkObsoleteSourcesSameDocumentNavigation) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
   Profile* profile = ProfileManager::GetActiveUserProfile();
   std::unique_ptr<ProfileSyncServiceHarness> harness =
@@ -1234,52 +1199,53 @@
 
   // First navigation.
   const ukm::SourceId source_id1 =
-      NavigateAndGetSource(sync_browser,
-                           embedded_test_server()->GetURL("/title1.html"))
+      NavigateAndGetSource(embedded_test_server()->GetURL("/title1.html"),
+                           sync_browser, &ukm_test_helper)
           ->id();
 
-  EXPECT_FALSE(IsSourceMarkedAsObsolete(source_id1));
+  EXPECT_FALSE(ukm_test_helper.IsSourceObsolete(source_id1));
 
   // Cross-document navigation where the previous navigation is cross-document.
   const ukm::SourceId source_id2 =
-      NavigateAndGetSource(sync_browser,
-                           embedded_test_server()->GetURL("/title2.html"))
+      NavigateAndGetSource(embedded_test_server()->GetURL("/title2.html"),
+                           sync_browser, &ukm_test_helper)
           ->id();
-  EXPECT_TRUE(IsSourceMarkedAsObsolete(source_id1));
-  EXPECT_FALSE(IsSourceMarkedAsObsolete(source_id2));
+  EXPECT_TRUE(ukm_test_helper.IsSourceObsolete(source_id1));
+  EXPECT_FALSE(ukm_test_helper.IsSourceObsolete(source_id2));
 
   // Same-document navigation where the previous navigation is cross-document.
   const ukm::SourceId source_id3 =
-      NavigateAndGetSource(sync_browser,
-                           embedded_test_server()->GetURL("/title2.html#a"))
+      NavigateAndGetSource(embedded_test_server()->GetURL("/title2.html#a"),
+                           sync_browser, &ukm_test_helper)
           ->id();
-  EXPECT_TRUE(IsSourceMarkedAsObsolete(source_id1));
-  EXPECT_FALSE(IsSourceMarkedAsObsolete(source_id2));
-  EXPECT_FALSE(IsSourceMarkedAsObsolete(source_id3));
+  EXPECT_TRUE(ukm_test_helper.IsSourceObsolete(source_id1));
+  EXPECT_FALSE(ukm_test_helper.IsSourceObsolete(source_id2));
+  EXPECT_FALSE(ukm_test_helper.IsSourceObsolete(source_id3));
 
   // Same-document navigation where the previous navigation is same-document.
   const ukm::SourceId source_id4 =
-      NavigateAndGetSource(sync_browser,
-                           embedded_test_server()->GetURL("/title2.html#b"))
+      NavigateAndGetSource(embedded_test_server()->GetURL("/title2.html#b"),
+                           sync_browser, &ukm_test_helper)
           ->id();
-  EXPECT_TRUE(IsSourceMarkedAsObsolete(source_id1));
-  EXPECT_FALSE(IsSourceMarkedAsObsolete(source_id2));
-  EXPECT_TRUE(IsSourceMarkedAsObsolete(source_id3));
-  EXPECT_FALSE(IsSourceMarkedAsObsolete(source_id4));
+  EXPECT_TRUE(ukm_test_helper.IsSourceObsolete(source_id1));
+  EXPECT_FALSE(ukm_test_helper.IsSourceObsolete(source_id2));
+  EXPECT_TRUE(ukm_test_helper.IsSourceObsolete(source_id3));
+  EXPECT_FALSE(ukm_test_helper.IsSourceObsolete(source_id4));
 
   // Cross-document navigation where the previous navigation is same-document.
-  NavigateAndGetSource(sync_browser,
-                       embedded_test_server()->GetURL("/title1.html"))
+  NavigateAndGetSource(embedded_test_server()->GetURL("/title1.html"),
+                       sync_browser, &ukm_test_helper)
       ->id();
-  EXPECT_TRUE(IsSourceMarkedAsObsolete(source_id1));
-  EXPECT_TRUE(IsSourceMarkedAsObsolete(source_id2));
-  EXPECT_TRUE(IsSourceMarkedAsObsolete(source_id3));
-  EXPECT_TRUE(IsSourceMarkedAsObsolete(source_id4));
+  EXPECT_TRUE(ukm_test_helper.IsSourceObsolete(source_id1));
+  EXPECT_TRUE(ukm_test_helper.IsSourceObsolete(source_id2));
+  EXPECT_TRUE(ukm_test_helper.IsSourceObsolete(source_id3));
+  EXPECT_TRUE(ukm_test_helper.IsSourceObsolete(source_id4));
 }
 
 // Verify that sources are not marked as obsolete by a new navigation that does
 // not commit.
 IN_PROC_BROWSER_TEST_F(UkmBrowserTest, NotMarkSourcesIfNavigationNotCommitted) {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
   MetricsConsentOverride metrics_consent(true);
   Profile* profile = ProfileManager::GetActiveUserProfile();
   std::unique_ptr<ProfileSyncServiceHarness> harness =
@@ -1298,14 +1264,15 @@
 
   // Get the source id from the committed navigation.
   const ukm::SourceId source_id =
-      NavigateAndGetSource(sync_browser, test_url_with_commit)->id();
+      NavigateAndGetSource(test_url_with_commit, sync_browser, &ukm_test_helper)
+          ->id();
 
   // Initial default state.
-  EXPECT_FALSE(IsSourceMarkedAsObsolete(source_id));
+  EXPECT_FALSE(ukm_test_helper.IsSourceObsolete(source_id));
 
   // New navigation did not commit, thus the source should still be kept alive.
   ui_test_utils::NavigateToURL(sync_browser, test_url_no_commit);
-  EXPECT_FALSE(IsSourceMarkedAsObsolete(source_id));
+  EXPECT_FALSE(ukm_test_helper.IsSourceObsolete(source_id));
 }
 
 }  // namespace metrics
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
index 8c68e2ef..4464bb3b 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
@@ -2857,6 +2857,9 @@
   histogram_tester_.ExpectBucketCount(
       internal::kHistogramBackForwardCacheEvent,
       internal::PageLoadBackForwardCacheEvent::kEnterBackForwardCache, 0);
+  histogram_tester_.ExpectBucketCount(
+      internal::kHistogramBackForwardCacheEvent,
+      internal::PageLoadBackForwardCacheEvent::kRestoreFromBackForwardCache, 0);
 
   // Go to URL2. The previous page (URL1) is put into the back-forward cache.
   ui_test_utils::NavigateToURL(browser(), url2);
@@ -2864,6 +2867,9 @@
   histogram_tester_.ExpectBucketCount(
       internal::kHistogramBackForwardCacheEvent,
       internal::PageLoadBackForwardCacheEvent::kEnterBackForwardCache, 1);
+  histogram_tester_.ExpectBucketCount(
+      internal::kHistogramBackForwardCacheEvent,
+      internal::PageLoadBackForwardCacheEvent::kRestoreFromBackForwardCache, 0);
 
   // Go back to URL1. The previous page (URL2) is put into the back-forward
   // cache.
@@ -2875,4 +2881,13 @@
   histogram_tester_.ExpectBucketCount(
       internal::kHistogramBackForwardCacheEvent,
       internal::PageLoadBackForwardCacheEvent::kEnterBackForwardCache, 2);
+
+  // For now CorePageLoadMetricsObserver::OnEnterBackForwardCache returns
+  // STOP_OBSERVING, OnRestoreFromBackForward is never reached.
+  //
+  // TODO(hajimehoshi): Update this when the CorePageLoadMetricsObserver
+  // continues to observe after entering to back-forward cache.
+  histogram_tester_.ExpectBucketCount(
+      internal::kHistogramBackForwardCacheEvent,
+      internal::PageLoadBackForwardCacheEvent::kRestoreFromBackForwardCache, 0);
 }
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index e11a6b11..6664109 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -470,7 +470,7 @@
   // Using unretained pointer is safe because |this| outlives
   // ContentPasswordManagerDriver that holds the connection.
   content_driver->GeneratePassword(base::BindOnce(
-      &ChromePasswordManagerClient::ShowManualPasswordGenerationPopup,
+      &ChromePasswordManagerClient::ManualGenerationResultAvailable,
       base::Unretained(this), base::AsWeakPtr(content_driver)));
 }
 
@@ -969,6 +969,18 @@
   password_manager::ContentPasswordManagerDriver* driver =
       driver_factory_->GetDriverForFrame(
           password_generation_driver_receivers_.GetCurrentTargetFrame());
+
+  // Attempt to show the autofill dropdown UI first.
+  gfx::RectF element_bounds_in_top_frame_space =
+      TransformToRootCoordinates(driver->render_frame_host(), ui_data.bounds);
+  if (driver->GetPasswordAutofillManager()
+          ->MaybeShowPasswordSuggestionsWithGeneration(
+              element_bounds_in_top_frame_space, ui_data.text_direction,
+              /*show_password_suggestions=*/
+              ui_data.is_generation_element_password_type)) {
+    return;
+  }
+
   ShowPasswordGenerationPopup(driver, ui_data,
                               false /* is_manually_triggered */);
 #endif  // defined(OS_ANDROID)
@@ -1252,7 +1264,7 @@
           scheme != content::kChromeDevToolsScheme);
 }
 
-void ChromePasswordManagerClient::ShowManualPasswordGenerationPopup(
+void ChromePasswordManagerClient::ManualGenerationResultAvailable(
     base::WeakPtr<password_manager::ContentPasswordManagerDriver> driver,
     const base::Optional<
         autofill::password_generation::PasswordGenerationUIData>& ui_data) {
@@ -1283,15 +1295,6 @@
     bool is_manually_triggered) {
   gfx::RectF element_bounds_in_top_frame_space =
       TransformToRootCoordinates(driver->render_frame_host(), ui_data.bounds);
-  // Only show password suggestions iff the field is of password type.
-  bool show_password_suggestions = ui_data.is_generation_element_password_type;
-  if (!is_manually_triggered &&
-      driver->GetPasswordAutofillManager()
-          ->MaybeShowPasswordSuggestionsWithGeneration(
-              element_bounds_in_top_frame_space, ui_data.text_direction,
-              show_password_suggestions)) {
-    return;
-  }
 
   gfx::RectF element_bounds_in_screen_space =
       GetBoundsInScreenSpace(element_bounds_in_top_frame_space);
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.h b/chrome/browser/password_manager/chrome_password_manager_client.h
index abb5911..df8eb8a 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.h
+++ b/chrome/browser/password_manager/chrome_password_manager_client.h
@@ -300,9 +300,10 @@
   // without custom sync passphrase.
   static bool ShouldAnnotateNavigationEntries(Profile* profile);
 
-  // |ui_data| is empty in case the renderer failed to start manual generation.
-  // In this case nothing should happen.
-  void ShowManualPasswordGenerationPopup(
+  // Called back by the PasswordGenerationAgent when the manual generation flow
+  // is completed. If |ui_data| is non-empty, will create a UI to display the
+  // generated password. Otherwise, nothing will happen.
+  void ManualGenerationResultAvailable(
       base::WeakPtr<password_manager::ContentPasswordManagerDriver> driver,
       const base::Optional<
           autofill::password_generation::PasswordGenerationUIData>& ui_data);
diff --git a/chrome/browser/password_manager/password_store_x_unittest.cc b/chrome/browser/password_manager/password_store_x_unittest.cc
index 06e84da..d621a82 100644
--- a/chrome/browser/password_manager/password_store_x_unittest.cc
+++ b/chrome/browser/password_manager/password_store_x_unittest.cc
@@ -28,7 +28,6 @@
 #include "components/password_manager/core/browser/password_manager_test_utils.h"
 #include "components/password_manager/core/browser/password_store_change.h"
 #include "components/password_manager/core/browser/password_store_consumer.h"
-#include "components/password_manager/core/browser/password_store_origin_unittest.h"
 #include "components/password_manager/core/common/password_manager_features.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -138,7 +137,7 @@
   // Check the contents are still around.
   MockPasswordStoreConsumer consumer;
   EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(
-                            ElementsAre(Pointee(MakePasswordForm()))));
+                            testing::ElementsAre(Pointee(MakePasswordForm()))));
   store->GetAutofillableLogins(&consumer);
 
   WaitForPasswordStore();
diff --git a/chrome/browser/payments/android/payment_app_service_bridge.cc b/chrome/browser/payments/android/payment_app_service_bridge.cc
index 5d9ee84..beb771c0 100644
--- a/chrome/browser/payments/android/payment_app_service_bridge.cc
+++ b/chrome/browser/payments/android/payment_app_service_bridge.cc
@@ -20,24 +20,26 @@
 #include "components/payments/content/payment_app_service.h"
 #include "components/payments/content/payment_app_service_factory.h"
 #include "components/payments/content/payment_manifest_web_data_service.h"
+#include "components/url_formatter/elide_url.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/blink/public/mojom/payments/payment_app.mojom.h"
 #include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
+#include "ui/gfx/android/java_bitmap.h"
 #include "url/origin.h"
 
 namespace {
-
 using ::base::android::AttachCurrentThread;
 using ::base::android::ConvertJavaStringToUTF8;
 using ::base::android::ConvertUTF8ToJavaString;
 using ::base::android::JavaParamRef;
 using ::base::android::JavaRef;
 using ::base::android::ScopedJavaGlobalRef;
+using ::base::android::ScopedJavaLocalRef;
+using ::base::android::ToJavaArrayOfStrings;
+using ::base::android::ToJavaIntArray;
 using ::payments::android::DeserializeFromJavaByteBufferArray;
-using ::payments::mojom::BasicCardNetwork;
-using ::payments::mojom::PaymentMethodData;
 using ::payments::mojom::PaymentMethodDataPtr;
 
 // Helper to get the PaymentAppService associated with |render_frame_host|'s
@@ -50,9 +52,82 @@
       web_contents ? web_contents->GetBrowserContext() : nullptr);
 }
 
-void OnPaymentAppCreated(const JavaRef<jobject>& jcallback) {
+void OnPaymentAppsCreated(
+    const JavaRef<jobject>& jcallback,
+    const content::PaymentAppProvider::PaymentApps& apps,
+    const payments::ServiceWorkerPaymentAppFinder::InstallablePaymentApps&
+        installable_apps) {
   JNIEnv* env = AttachCurrentThread();
-  Java_PaymentAppServiceCallback_onPaymentAppCreated(env, jcallback);
+
+  for (const auto& app_info : apps) {
+    // Sends related application Ids to java side if the app prefers related
+    // applications.
+    std::vector<std::string> preferred_related_application_ids;
+    if (app_info.second->prefer_related_applications) {
+      for (const auto& related_application :
+           app_info.second->related_applications) {
+        // Only consider related applications on Google play for Android.
+        if (related_application.platform == "play")
+          preferred_related_application_ids.emplace_back(
+              related_application.id);
+      }
+    }
+
+    base::android::ScopedJavaLocalRef<jobjectArray> jcapabilities =
+        Java_PaymentAppServiceBridge_createCapabilities(
+            env, app_info.second->capabilities.size());
+    for (size_t i = 0; i < app_info.second->capabilities.size(); i++) {
+      Java_PaymentAppServiceBridge_addCapabilities(
+          env, jcapabilities, base::checked_cast<int>(i),
+          ToJavaIntArray(
+              env, app_info.second->capabilities[i].supported_card_networks));
+    }
+
+    base::android::ScopedJavaLocalRef<jobject> jsupported_delegations =
+        Java_PaymentAppServiceBridge_createSupportedDelegations(
+            env, app_info.second->supported_delegations.shipping_address,
+            app_info.second->supported_delegations.payer_name,
+            app_info.second->supported_delegations.payer_phone,
+            app_info.second->supported_delegations.payer_email);
+
+    // TODO(crbug.com/846077): Find a proper way to make use of user hint.
+    Java_PaymentAppServiceCallback_onInstalledPaymentHandlerFound(
+        env, jcallback, app_info.second->registration_id,
+        ConvertUTF8ToJavaString(env, app_info.second->scope.spec()),
+        app_info.second->name.empty()
+            ? nullptr
+            : ConvertUTF8ToJavaString(env, app_info.second->name),
+        nullptr, ConvertUTF8ToJavaString(env, app_info.second->scope.host()),
+        app_info.second->icon == nullptr
+            ? nullptr
+            : gfx::ConvertToJavaBitmap(app_info.second->icon.get()),
+        ToJavaArrayOfStrings(env, app_info.second->enabled_methods),
+        app_info.second->has_explicitly_verified_methods, jcapabilities,
+        ToJavaArrayOfStrings(env, preferred_related_application_ids),
+        jsupported_delegations);
+  }
+
+  for (const auto& installable_app : installable_apps) {
+    base::android::ScopedJavaLocalRef<jobject> jsupported_delegations =
+        Java_PaymentAppServiceBridge_createSupportedDelegations(
+            env, installable_app.second->supported_delegations.shipping_address,
+            installable_app.second->supported_delegations.payer_name,
+            installable_app.second->supported_delegations.payer_phone,
+            installable_app.second->supported_delegations.payer_email);
+
+    Java_PaymentAppServiceCallback_onInstallablePaymentHandlerFound(
+        env, jcallback,
+        ConvertUTF8ToJavaString(env, installable_app.second->name),
+        ConvertUTF8ToJavaString(env, installable_app.second->sw_js_url),
+        ConvertUTF8ToJavaString(env, installable_app.second->sw_scope),
+        installable_app.second->sw_use_cache,
+        installable_app.second->icon == nullptr
+            ? nullptr
+            : gfx::ConvertToJavaBitmap(installable_app.second->icon.get()),
+        ConvertUTF8ToJavaString(env, installable_app.first.spec()),
+        ToJavaArrayOfStrings(env, installable_app.second->preferred_app_ids),
+        jsupported_delegations);
+  }
 }
 
 void OnPaymentAppCreationError(const JavaRef<jobject>& jcallback,
@@ -99,7 +174,7 @@
       service->GetNumberOfFactories(), render_frame_host, GURL(top_origin),
       std::move(method_data), web_data_service,
       jmay_crawl_for_installable_payment_apps,
-      base::BindRepeating(&OnPaymentAppCreated,
+      base::BindRepeating(&OnPaymentAppsCreated,
                           ScopedJavaGlobalRef<jobject>(env, jcallback)),
       base::BindRepeating(&OnPaymentAppCreationError,
                           ScopedJavaGlobalRef<jobject>(env, jcallback)),
@@ -151,14 +226,14 @@
     std::vector<mojom::PaymentMethodDataPtr> request_method_data,
     scoped_refptr<PaymentManifestWebDataService> web_data_service,
     bool may_crawl_for_installable_payment_apps,
-    PaymentAppCreatedCallback payment_app_created_callback,
+    PaymentAppsCreatedCallback payment_apps_created_callback,
     PaymentAppCreationErrorCallback payment_app_creation_error_callback,
     base::OnceClosure done_creating_payment_apps_callback) {
   std::unique_ptr<PaymentAppServiceBridge> bridge(new PaymentAppServiceBridge(
       number_of_factories, render_frame_host, top_origin,
       std::move(request_method_data), std::move(web_data_service),
       may_crawl_for_installable_payment_apps,
-      std::move(payment_app_created_callback),
+      std::move(payment_apps_created_callback),
       std::move(payment_app_creation_error_callback),
       std::move(done_creating_payment_apps_callback)));
   return PaymentAppServiceBridgeStorage::GetInstance()->Add(std::move(bridge));
@@ -171,7 +246,7 @@
     std::vector<mojom::PaymentMethodDataPtr> request_method_data,
     scoped_refptr<PaymentManifestWebDataService> web_data_service,
     bool may_crawl_for_installable_payment_apps,
-    PaymentAppCreatedCallback payment_app_created_callback,
+    PaymentAppsCreatedCallback payment_apps_created_callback,
     PaymentAppCreationErrorCallback payment_app_creation_error_callback,
     base::OnceClosure done_creating_payment_apps_callback)
     : number_of_pending_factories_(number_of_factories),
@@ -179,13 +254,14 @@
           content::WebContents::FromRenderFrameHost(render_frame_host)),
       render_frame_host_(render_frame_host),
       top_origin_(top_origin),
-      frame_origin_(render_frame_host->GetLastCommittedURL()),
+      frame_origin_(url_formatter::FormatUrlForSecurityDisplay(
+          render_frame_host->GetLastCommittedURL())),
       frame_security_origin_(render_frame_host->GetLastCommittedOrigin()),
       request_method_data_(std::move(request_method_data)),
       payment_manifest_web_data_service_(web_data_service),
       may_crawl_for_installable_payment_apps_(
           may_crawl_for_installable_payment_apps),
-      payment_app_created_callback_(std::move(payment_app_created_callback)),
+      payment_apps_created_callback_(std::move(payment_apps_created_callback)),
       payment_app_creation_error_callback_(
           std::move(payment_app_creation_error_callback)),
       done_creating_payment_apps_callback_(
@@ -271,11 +347,9 @@
 }
 
 void PaymentAppServiceBridge::OnCreatingNativePaymentAppsSkipped(
-    const content::PaymentAppProvider::PaymentApps& apps,
-    const ServiceWorkerPaymentAppFinder::InstallablePaymentApps&
-        installable_apps) {
-  // TODO(crbug.com/1063118): call back to Java with apps information.
-  payment_app_created_callback_.Run();
+    content::PaymentAppProvider::PaymentApps apps,
+    ServiceWorkerPaymentAppFinder::InstallablePaymentApps installable_apps) {
+  payment_apps_created_callback_.Run(apps, installable_apps);
 }
 
 void PaymentAppServiceBridge::OnPaymentAppCreationError(
diff --git a/chrome/browser/payments/android/payment_app_service_bridge.h b/chrome/browser/payments/android/payment_app_service_bridge.h
index ccd3cd33..269aa12 100644
--- a/chrome/browser/payments/android/payment_app_service_bridge.h
+++ b/chrome/browser/payments/android/payment_app_service_bridge.h
@@ -32,9 +32,9 @@
 // callbacks from PaymentAppFactory to callbacks set by the caller.
 class PaymentAppServiceBridge : public PaymentAppFactory::Delegate {
  public:
-  // TODO(crbug.com/1063118): add more parameter to this callback to actually
-  // pass payment app data back to Java side.
-  using PaymentAppCreatedCallback = base::RepeatingCallback<void()>;
+  using PaymentAppsCreatedCallback = base::RepeatingCallback<void(
+      const content::PaymentAppProvider::PaymentApps&,
+      const payments::ServiceWorkerPaymentAppFinder::InstallablePaymentApps&)>;
   using PaymentAppCreationErrorCallback =
       base::RepeatingCallback<void(const std::string&)>;
 
@@ -48,7 +48,7 @@
       std::vector<mojom::PaymentMethodDataPtr> request_method_data,
       scoped_refptr<PaymentManifestWebDataService> web_data_service,
       bool may_crawl_for_installable_payment_apps,
-      PaymentAppCreatedCallback payment_app_created_callback,
+      PaymentAppsCreatedCallback payment_apps_created_callback,
       PaymentAppCreationErrorCallback payment_app_creation_error_callback,
       base::OnceClosure done_creating_payment_apps_callback);
 
@@ -81,9 +81,9 @@
   void OnPaymentAppCreationError(const std::string& error_message) override;
   bool SkipCreatingNativePaymentApps() const override;
   void OnCreatingNativePaymentAppsSkipped(
-      const content::PaymentAppProvider::PaymentApps& apps,
-      const ServiceWorkerPaymentAppFinder::InstallablePaymentApps&
-          installable_apps) override;
+      content::PaymentAppProvider::PaymentApps apps,
+      ServiceWorkerPaymentAppFinder::InstallablePaymentApps installable_apps)
+      override;
   void OnDoneCreatingPaymentApps() override;
 
  private:
@@ -95,7 +95,7 @@
       std::vector<mojom::PaymentMethodDataPtr> request_method_data,
       scoped_refptr<PaymentManifestWebDataService> web_data_service,
       bool may_crawl_for_installable_payment_apps,
-      PaymentAppCreatedCallback payment_app_created_callback,
+      PaymentAppsCreatedCallback payment_apps_created_callback,
       PaymentAppCreationErrorCallback payment_app_creation_error_callback,
       base::OnceClosure done_creating_payment_apps_callback);
 
@@ -111,7 +111,7 @@
   bool may_crawl_for_installable_payment_apps_;
   std::vector<autofill::AutofillProfile*> dummy_profiles_;
 
-  PaymentAppCreatedCallback payment_app_created_callback_;
+  PaymentAppsCreatedCallback payment_apps_created_callback_;
   PaymentAppCreationErrorCallback payment_app_creation_error_callback_;
   base::OnceClosure done_creating_payment_apps_callback_;
 
diff --git a/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc b/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc
index 2d731b0..74983bfe 100644
--- a/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc
+++ b/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc
@@ -24,7 +24,10 @@
 class MockCallback {
  public:
   MockCallback() = default;
-  MOCK_METHOD0(NotifyPaymentAppCreated, void(void));
+  MOCK_METHOD2(NotifyPaymentAppsCreated,
+               void(const content::PaymentAppProvider::PaymentApps&,
+                    const payments::ServiceWorkerPaymentAppFinder::
+                        InstallablePaymentApps&));
   MOCK_METHOD1(NotifyPaymentAppCreationError, void(const std::string& error));
   MOCK_METHOD0(NotifyDoneCreatingPaymentApps, void(void));
 };
@@ -68,7 +71,7 @@
           /* number_of_factories= */ 3, web_contents_->GetMainFrame(),
           top_origin_, std::move(method_data), web_data_service_,
           /* may_crawl_for_installable_payment_apps= */ true,
-          base::BindRepeating(&MockCallback::NotifyPaymentAppCreated,
+          base::BindRepeating(&MockCallback::NotifyPaymentAppsCreated,
                               base::Unretained(&mock_callback)),
           base::BindRepeating(&MockCallback::NotifyPaymentAppCreationError,
                               base::Unretained(&mock_callback)),
@@ -92,8 +95,10 @@
   content::PaymentAppProvider::PaymentApps apps;
   ServiceWorkerPaymentAppFinder::InstallablePaymentApps installables;
 
-  EXPECT_CALL(mock_callback, NotifyPaymentAppCreated());
-  bridge->OnCreatingNativePaymentAppsSkipped(apps, installables);
+  EXPECT_CALL(mock_callback,
+              NotifyPaymentAppsCreated(::testing::_, ::testing::_));
+  bridge->OnCreatingNativePaymentAppsSkipped(std::move(apps),
+                                             std::move(installables));
 
   EXPECT_CALL(mock_callback, NotifyPaymentAppCreationError("some error"));
   bridge->OnPaymentAppCreationError("some error");
diff --git a/chrome/browser/payments/android/service_worker_payment_app_bridge.cc b/chrome/browser/payments/android/service_worker_payment_app_bridge.cc
index 9b1efd2..f86da50 100644
--- a/chrome/browser/payments/android/service_worker_payment_app_bridge.cc
+++ b/chrome/browser/payments/android/service_worker_payment_app_bridge.cc
@@ -60,92 +60,6 @@
 using ::payments::mojom::PaymentShippingOptionPtr;
 using ::payments::mojom::PaymentShippingType;
 
-void OnGotAllPaymentApps(
-    const JavaRef<jobject>& jcallback,
-    content::PaymentAppProvider::PaymentApps apps,
-    payments::ServiceWorkerPaymentAppFinder::InstallablePaymentApps
-        installable_apps,
-    const std::string& error_message) {
-  JNIEnv* env = AttachCurrentThread();
-
-  if (!error_message.empty()) {
-    Java_PaymentHandlerFinder_onGetPaymentAppsError(
-        env, jcallback, ConvertUTF8ToJavaString(env, error_message));
-  }
-
-  for (const auto& app_info : apps) {
-    // Sends related application Ids to java side if the app prefers related
-    // applications.
-    std::vector<std::string> preferred_related_application_ids;
-    if (app_info.second->prefer_related_applications) {
-      for (const auto& related_application :
-           app_info.second->related_applications) {
-        // Only consider related applications on Google play for Android.
-        if (related_application.platform == "play")
-          preferred_related_application_ids.emplace_back(
-              related_application.id);
-      }
-    }
-
-    base::android::ScopedJavaLocalRef<jobjectArray> jcapabilities =
-        Java_ServiceWorkerPaymentAppBridge_createCapabilities(
-            env, app_info.second->capabilities.size());
-    for (size_t i = 0; i < app_info.second->capabilities.size(); i++) {
-      Java_ServiceWorkerPaymentAppBridge_addCapabilities(
-          env, jcapabilities, base::checked_cast<int>(i),
-          ToJavaIntArray(
-              env, app_info.second->capabilities[i].supported_card_networks));
-    }
-
-    base::android::ScopedJavaLocalRef<jobject> jsupported_delegations =
-        Java_ServiceWorkerPaymentAppBridge_createSupportedDelegations(
-            env, app_info.second->supported_delegations.shipping_address,
-            app_info.second->supported_delegations.payer_name,
-            app_info.second->supported_delegations.payer_phone,
-            app_info.second->supported_delegations.payer_email);
-
-    // TODO(crbug.com/846077): Find a proper way to make use of user hint.
-    Java_PaymentHandlerFinder_onInstalledPaymentHandlerFound(
-        env, jcallback, app_info.second->registration_id,
-        ConvertUTF8ToJavaString(env, app_info.second->scope.spec()),
-        app_info.second->name.empty()
-            ? nullptr
-            : ConvertUTF8ToJavaString(env, app_info.second->name),
-        nullptr, ConvertUTF8ToJavaString(env, app_info.second->scope.host()),
-        app_info.second->icon == nullptr
-            ? nullptr
-            : gfx::ConvertToJavaBitmap(app_info.second->icon.get()),
-        ToJavaArrayOfStrings(env, app_info.second->enabled_methods),
-        app_info.second->has_explicitly_verified_methods, jcapabilities,
-        ToJavaArrayOfStrings(env, preferred_related_application_ids),
-        jsupported_delegations);
-  }
-
-  for (const auto& installable_app : installable_apps) {
-    base::android::ScopedJavaLocalRef<jobject> jsupported_delegations =
-        Java_ServiceWorkerPaymentAppBridge_createSupportedDelegations(
-            env, installable_app.second->supported_delegations.shipping_address,
-            installable_app.second->supported_delegations.payer_name,
-            installable_app.second->supported_delegations.payer_phone,
-            installable_app.second->supported_delegations.payer_email);
-
-    Java_PaymentHandlerFinder_onInstallablePaymentHandlerFound(
-        env, jcallback,
-        ConvertUTF8ToJavaString(env, installable_app.second->name),
-        ConvertUTF8ToJavaString(env, installable_app.second->sw_js_url),
-        ConvertUTF8ToJavaString(env, installable_app.second->sw_scope),
-        installable_app.second->sw_use_cache,
-        installable_app.second->icon == nullptr
-            ? nullptr
-            : gfx::ConvertToJavaBitmap(installable_app.second->icon.get()),
-        ConvertUTF8ToJavaString(env, installable_app.first.spec()),
-        ToJavaArrayOfStrings(env, installable_app.second->preferred_app_ids),
-        jsupported_delegations);
-  }
-
-  Java_PaymentHandlerFinder_onAllPaymentAppsCreated(env, jcallback);
-}
-
 void OnHasServiceWorkerPaymentAppsResponse(
     const JavaRef<jobject>& jcallback,
     content::PaymentAppProvider::PaymentApps apps) {
@@ -178,11 +92,10 @@
 }
 
 void OnCanMakePayment(const JavaRef<jobject>& jcallback,
-                      const JavaRef<jobject>& japp,
                       payments::mojom::CanMakePaymentResponsePtr response) {
   JNIEnv* env = AttachCurrentThread();
-  Java_PaymentHandlerFinder_onCanMakePaymentEventResponse(
-      env, jcallback, japp,
+  Java_ServiceWorkerPaymentAppBridge_onCanMakePaymentEventResponse(
+      env, jcallback,
       ConvertUTF8ToJavaString(
           env, payments::ConvertCanMakePaymentEventResponseTypeToErrorString(
                    response->response_type)),
@@ -419,35 +332,6 @@
 
 }  // namespace
 
-static void JNI_ServiceWorkerPaymentAppBridge_GetAllPaymentApps(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& jorigin,
-    const JavaParamRef<jobject>& jrender_frame_host,
-    const JavaParamRef<jobjectArray>& jmethod_data,
-    jboolean jmay_crawl_for_installable_payment_apps,
-    const JavaParamRef<jobject>& jcallback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  content::RenderFrameHost* render_frame_host =
-      content::RenderFrameHost::FromJavaRenderFrameHost(jrender_frame_host);
-  content::WebContents* web_contents =
-      content::WebContents::FromRenderFrameHost(render_frame_host);
-
-  payments::ServiceWorkerPaymentAppFinder::GetInstance()->GetAllPaymentApps(
-      url::Origin::FromJavaObject(jorigin), render_frame_host, web_contents,
-      WebDataServiceFactory::GetPaymentManifestWebDataForProfile(
-          Profile::FromBrowserContext(web_contents->GetBrowserContext()),
-          ServiceAccessType::EXPLICIT_ACCESS),
-      ConvertPaymentMethodDataFromJavaToNative(env, jmethod_data),
-      jmay_crawl_for_installable_payment_apps,
-      base::BindOnce(&OnGotAllPaymentApps,
-                     ScopedJavaGlobalRef<jobject>(env, jcallback)),
-      base::BindOnce([]() {
-        /* Nothing needs to be done after writing cache. This callback is used
-         * only in tests. */
-      }));
-}
-
 static void JNI_ServiceWorkerPaymentAppBridge_HasServiceWorkerPaymentApps(
     JNIEnv* env,
     const JavaParamRef<jobject>& jcallback) {
@@ -479,8 +363,7 @@
     const JavaParamRef<jobjectArray>& jmethod_data,
     const JavaParamRef<jobjectArray>& jmodifiers,
     const JavaParamRef<jstring>& jcurrency,
-    const JavaParamRef<jobject>& jcallback,
-    const JavaParamRef<jobject>& japp) {
+    const JavaParamRef<jobject>& jcallback) {
   content::WebContents* web_contents =
       content::WebContents::FromJavaWebContents(jweb_contents);
 
@@ -533,8 +416,7 @@
           GURL(ConvertJavaStringToUTF8(env, jservice_worker_scope))),
       ConvertJavaStringToUTF8(env, jpayment_request_id), std::move(event_data),
       base::BindOnce(&OnCanMakePayment,
-                     ScopedJavaGlobalRef<jobject>(env, jcallback),
-                     ScopedJavaGlobalRef<jobject>(env, japp)));
+                     ScopedJavaGlobalRef<jobject>(env, jcallback)));
 }
 
 static void JNI_ServiceWorkerPaymentAppBridge_InvokePaymentApp(
diff --git a/chrome/browser/performance_manager/BUILD.gn b/chrome/browser/performance_manager/BUILD.gn
deleted file mode 100644
index a0a3843..0000000
--- a/chrome/browser/performance_manager/BUILD.gn
+++ /dev/null
@@ -1,10 +0,0 @@
-# Copyright 2019 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//chrome/common/features.gni")
-import("//third_party/protobuf/proto_library.gni")
-
-proto_library("site_data_proto") {
-  sources = [ "persistence/site_data/site_data.proto" ]
-}
diff --git a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.cc b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.cc
index 63e15a0..6524b7f 100644
--- a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.cc
+++ b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.cc
@@ -20,19 +20,18 @@
 // Pointer to the instance of itself.
 BackgroundTabLoadingPolicy* g_background_tab_loading_policy = nullptr;
 
-// Lower bound for the maximum number of tabs to load simultaneously.
-uint32_t kMinSimultaneousTabLoads = 1;
-
-// Upper bound for the maximum number of tabs to load simultaneously.
-// Setting to zero means no upper bound is applied.
-uint32_t kMaxSimultaneousTabLoads = 4;
-
-// The number of CPU cores required per permitted simultaneous tab
-// load. Setting to zero means no CPU core limit applies.
-uint32_t kCoresPerSimultaneousTabLoad = 2;
-
 }  // namespace
 
+// static
+constexpr uint32_t BackgroundTabLoadingPolicy::kMinTabsToLoad;
+constexpr uint32_t BackgroundTabLoadingPolicy::kMaxTabsToLoad;
+constexpr uint32_t BackgroundTabLoadingPolicy::kDesiredAmountOfFreeMemoryMb;
+constexpr base::TimeDelta
+    BackgroundTabLoadingPolicy::kMaxTimeSinceLastUseToLoad;
+constexpr uint32_t BackgroundTabLoadingPolicy::kMinSimultaneousTabLoads;
+constexpr uint32_t BackgroundTabLoadingPolicy::kMaxSimultaneousTabLoads;
+constexpr uint32_t BackgroundTabLoadingPolicy::kCoresPerSimultaneousTabLoad;
+
 void ScheduleLoadForRestoredTabs(
     std::vector<content::WebContents*> web_contents_vector) {
   std::vector<base::WeakPtr<PageNode>> weakptr_page_nodes;
@@ -136,16 +135,49 @@
   max_simultaneous_tab_loads_ = loading_slots;
 }
 
+void BackgroundTabLoadingPolicy::SetFreeMemoryForTesting(
+    size_t free_memory_mb) {
+  free_memory_mb_for_testing_ = free_memory_mb;
+}
+
+void BackgroundTabLoadingPolicy::ResetPolicyForTesting() {
+  tab_loads_started_ = 0;
+}
+
 BackgroundTabLoadingPolicy* BackgroundTabLoadingPolicy::GetInstance() {
   return g_background_tab_loading_policy;
 }
 
+bool BackgroundTabLoadingPolicy::ShouldLoad(const PageNode* page_node) {
+  if (tab_loads_started_ < kMinTabsToLoad)
+    return true;
+
+  if (tab_loads_started_ >= kMaxTabsToLoad)
+    return false;
+
+  // If there is a free memory constraint then enforce it.
+  size_t free_memory_mb = GetFreePhysicalMemoryMib();
+  if (free_memory_mb < kDesiredAmountOfFreeMemoryMb)
+    return false;
+
+  // Enforce a max time since last use.
+  if (page_node->GetTimeSinceLastVisibilityChange() >
+      kMaxTimeSinceLastUseToLoad) {
+    return false;
+  }
+
+  // TODO: Enforce the site engagement score for tabs that don't make use of
+  // background communication mechanisms.
+  return true;
+}
+
 void BackgroundTabLoadingPolicy::InitiateLoad(const PageNode* page_node) {
   // Mark |page_node| as load initiated. Ensure that InitiateLoad is only called
   // for a PageNode that is tracked by the policy.
   size_t num_removed = base::Erase(page_nodes_to_load_, page_node);
   DCHECK_EQ(num_removed, 1U);
   page_nodes_load_initiated_.push_back(page_node);
+  tab_loads_started_++;
 
   // Make the call to load |page_node|.
   page_loader_->LoadPageNode(page_node);
@@ -190,9 +222,27 @@
   DCHECK(!page_nodes_to_load_.empty());
 
   // Find the next PageNode to load.
-  const PageNode* page_node = page_nodes_to_load_.front();
+  while (!page_nodes_to_load_.empty()) {
+    const PageNode* page_node = page_nodes_to_load_.front();
+    if (ShouldLoad(page_node)) {
+      InitiateLoad(page_node);
+      return;
+    }
 
-  InitiateLoad(page_node);
+    // |page_node| should not be loaded at this time. Remove |page_node| from
+    // the policy.
+    base::Erase(page_nodes_to_load_, page_node);
+  }
+}
+
+size_t BackgroundTabLoadingPolicy::GetFreePhysicalMemoryMib() const {
+  if (free_memory_mb_for_testing_ != 0)
+    return free_memory_mb_for_testing_;
+  constexpr int64_t kMibibytesInBytes = 1 << 20;
+  int64_t free_mem =
+      base::SysInfo::AmountOfAvailablePhysicalMemory() / kMibibytesInBytes;
+  DCHECK_GE(free_mem, 0);
+  return free_mem;
 }
 
 }  // namespace policies
diff --git a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.h b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.h
index 9386a79..81001e10 100644
--- a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.h
+++ b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.h
@@ -43,11 +43,19 @@
 
   void SetMockLoaderForTesting(std::unique_ptr<mechanism::PageLoader> loader);
   void SetMaxSimultaneousLoadsForTesting(size_t loading_slots);
+  void SetFreeMemoryForTesting(size_t free_memory_mb);
+  void ResetPolicyForTesting();
 
   // Returns the instance of BackgroundTabLoadingPolicy within the graph.
   static BackgroundTabLoadingPolicy* GetInstance();
 
  private:
+  // Determines whether or not the given PageNode should be loaded. If this
+  // returns false, then the policy no longer attempts to load |page_node| and
+  // removes it from the policy's internal state. This is called immediately
+  // prior to trying to load the PageNode.
+  bool ShouldLoad(const PageNode* page_node);
+
   // Move the PageNode from |page_nodes_to_load_| to
   // |page_nodes_load_initiated_| and make the call to load the PageNode.
   void InitiateLoad(const PageNode* page_node);
@@ -69,6 +77,9 @@
   // simultaneously loading tabs is exceeded.
   void LoadNextTab();
 
+  // Compute the amount of free memory on the system.
+  size_t GetFreePhysicalMemoryMib() const;
+
   // The mechanism used to load the pages.
   std::unique_ptr<performance_manager::mechanism::PageLoader> page_loader_;
 
@@ -87,6 +98,44 @@
   // The number of simultaneous tab loads that are permitted by policy. This
   // is computed based on the number of cores on the machine.
   size_t max_simultaneous_tab_loads_;
+
+  // The number of tab loads that have started. Every call to InitiateLoad
+  // increments this value.
+  size_t tab_loads_started_ = 0;
+
+  // Used to overwrite the amount of free memory available on the system.
+  size_t free_memory_mb_for_testing_ = 0;
+
+  // The minimum total number of restored tabs to load.
+  static constexpr uint32_t kMinTabsToLoad = 4;
+
+  // The maximum total number of restored tabs to load.
+  static constexpr uint32_t kMaxTabsToLoad = 20;
+
+  // The minimum amount of memory to keep free.
+  static constexpr uint32_t kDesiredAmountOfFreeMemoryMb = 150;
+
+  // The maximum time since last use of a tab in order for it to be loaded.
+  static constexpr base::TimeDelta kMaxTimeSinceLastUseToLoad =
+      base::TimeDelta::FromDays(30);
+
+  // Lower bound for the maximum number of tabs to load simultaneously.
+  static constexpr uint32_t kMinSimultaneousTabLoads = 1;
+
+  // Upper bound for the maximum number of tabs to load simultaneously.
+  static constexpr uint32_t kMaxSimultaneousTabLoads = 4;
+
+  // The number of CPU cores required per permitted simultaneous tab
+  // load.
+  static constexpr uint32_t kCoresPerSimultaneousTabLoad = 2;
+
+  FRIEND_TEST_ALL_PREFIXES(BackgroundTabLoadingPolicyTest,
+                           ShouldLoad_MaxTabsToRestore);
+  FRIEND_TEST_ALL_PREFIXES(BackgroundTabLoadingPolicyTest,
+                           ShouldLoad_MinTabsToRestore);
+  FRIEND_TEST_ALL_PREFIXES(BackgroundTabLoadingPolicyTest,
+                           ShouldLoad_FreeMemory);
+  FRIEND_TEST_ALL_PREFIXES(BackgroundTabLoadingPolicyTest, ShouldLoad_OldTab);
 };
 
 }  // namespace policies
diff --git a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers.cc b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers.cc
index 6ab117b..4881c010 100644
--- a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers.cc
+++ b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers.cc
@@ -3,6 +3,9 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers.h"
+
+#include <math.h>
+
 #include "base/logging.h"
 
 namespace performance_manager {
@@ -34,6 +37,47 @@
   return loads;
 }
 
+float CalculateAgeScore(double last_visibility_change_seconds) {
+  // TODO(crbug.com/1059341): Determine via an experiment whether tabs could
+  // simply be sorted by descending order of last visibility, instead of using
+  // an opaque score.
+
+  // Cap absolute values less than 1 so that the inverse will be between -1
+  // and 1.
+  double score = last_visibility_change_seconds;
+  if (fabs(score) < 1.0f) {
+    if (score > 0)
+      score = 1;
+    else
+      score = -1;
+  }
+  DCHECK_LE(1.0f, fabs(score));
+
+  // Invert the score (1 / score).
+  // Really old (infinity) maps to 0 (lowest priority).
+  // Really young positive age (1) maps to 1 (moderate priority).
+  // A little in the future (-1) maps to -1 (moderate priority).
+  // Really far in the future (-infinity) maps to 0 (highest priority).
+  // Shifting negative scores from [-1, 0] to [1, 2] keeps the scores increasing
+  // with priority.
+  if (score < 0) {
+    score = 2.0 + 1.0 / score;
+  } else {
+    score = 1.0 / score;
+  }
+  DCHECK_LE(0.0, score);
+  DCHECK_GE(2.0, score);
+
+  // Rescale the age score to the range [0, 1] so that it can be added to the
+  // category scores already calculated. Divide by 2 + epsilon so that no
+  // score will end up rounding up to 1.0, but instead be capped at 0.999.
+  score /= 2.002;
+  DCHECK_LE(0.0, score);
+  DCHECK_GT(1.0, score);
+
+  return score;
+}
+
 }  // namespace policies
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers.h b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers.h
index fae53ab1..249e226e 100644
--- a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers.h
+++ b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers.h
@@ -18,6 +18,11 @@
                                         size_t cores_per_load,
                                         size_t num_cores);
 
+// Calculates a score for the "age" of the tab. This is a value between 0
+// (inclusive) and 1 (exclusive), where higher values are attributed to newer
+// tabs.
+float CalculateAgeScore(double last_visibility_change_seconds);
+
 }  // namespace policies
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers_unittest.cc b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers_unittest.cc
index 4429a0e9..2b59251 100644
--- a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers_unittest.cc
+++ b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers_unittest.cc
@@ -4,6 +4,10 @@
 
 #include "chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_helpers.h"
 
+#include <math.h>
+
+#include "base/rand_util.h"
+#include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace performance_manager {
@@ -49,6 +53,87 @@
                                        0 /* cores_per_load */, 4 /* cores */));
 }
 
+TEST_F(BackgroundTabLoadingPolicyHelpersTest, CalculateAgeScore) {
+  // Generate a bunch of random tab age data.
+  std::vector<std::pair<base::TimeDelta, float>> tab_age_score;
+  tab_age_score.reserve(1000);
+
+  // Generate some known edge cases.
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromMilliseconds(-1001), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromMilliseconds(-1000), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromMilliseconds(-999), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromMilliseconds(-500), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromMilliseconds(0), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromMilliseconds(500), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromMilliseconds(999), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromMilliseconds(1000), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromMilliseconds(1001), 0.0));
+
+  // Generate a logarithmic selection of ages to test the whole range.
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(-1000000), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(-100000), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(-10000), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(-1000), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(-100), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(-10), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(10), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(100), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(1000), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(10000), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(100000), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(1000000), 0.0));
+  tab_age_score.push_back(
+      std::make_pair(base::TimeDelta::FromSeconds(10000000), 0.0));
+
+  constexpr int kMonthInSeconds = 60 * 60 * 24 * 31;
+
+  // Generate a bunch more random ages.
+  for (size_t i = tab_age_score.size(); i < 1000; ++i) {
+    tab_age_score.push_back(
+        std::make_pair(base::TimeDelta::FromSeconds(
+                           base::RandInt(-kMonthInSeconds, kMonthInSeconds)),
+                       0.0));
+  }
+
+  // Calculate the tab scores.
+  for (auto age_score : tab_age_score) {
+    age_score.second = CalculateAgeScore(age_score.first.InSecondsF());
+  }
+
+  // Sort tab scores by increasing last active time.
+  std::sort(tab_age_score.begin(), tab_age_score.end(),
+            [](const std::pair<base::TimeDelta, float>& tab1,
+               const std::pair<base::TimeDelta, float>& tab2) {
+              return tab1.first < tab2.first;
+            });
+
+  // The scores should be in decreasing order (>= is necessary because some
+  // last active times collapse to the same score).
+  for (size_t i = 1; i < tab_age_score.size(); ++i)
+    ASSERT_GE(tab_age_score[i - 1].second, tab_age_score[i].second);
+}
+
 }  // namespace policies
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_unittest.cc b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_unittest.cc
index b7dbe15..31e409d 100644
--- a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_unittest.cc
+++ b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_unittest.cc
@@ -53,6 +53,7 @@
     // Set a value explicitly for MaxSimultaneousLoad threshold to avoid a
     // dependency on the number of cores of the machine on which the test runs.
     policy_->SetMaxSimultaneousLoadsForTesting(4);
+    policy_->SetFreeMemoryForTesting(150);
   }
 
   void TearDown() override { graph()->TakeFromGraph(policy_); }
@@ -125,6 +126,91 @@
   page_node_impl->SetIsLoading(false);
 }
 
+TEST_F(BackgroundTabLoadingPolicyTest, ShouldLoad_MaxTabsToRestore) {
+  // Create vector of PageNodes to restore.
+  std::vector<
+      performance_manager::TestNodeWrapper<performance_manager::PageNodeImpl>>
+      page_nodes;
+  std::vector<PageNode*> raw_page_nodes;
+
+  for (uint32_t i = 0; i < policy()->kMaxTabsToLoad + 1; i++) {
+    page_nodes.push_back(CreateNode<performance_manager::PageNodeImpl>());
+    raw_page_nodes.push_back(page_nodes.back().get());
+  }
+
+  // Test the maximum number of tabs to load threshold.
+  for (uint32_t i = 0; i < policy()->kMaxTabsToLoad; i++) {
+    EXPECT_TRUE(policy()->ShouldLoad(raw_page_nodes[i]));
+    policy()->tab_loads_started_++;
+  }
+  EXPECT_FALSE(policy()->ShouldLoad(raw_page_nodes[policy()->kMaxTabsToLoad]));
+}
+
+TEST_F(BackgroundTabLoadingPolicyTest, ShouldLoad_MinTabsToRestore) {
+  // Create vector of PageNodes to restore.
+  std::vector<
+      performance_manager::TestNodeWrapper<performance_manager::PageNodeImpl>>
+      page_nodes;
+  std::vector<PageNode*> raw_page_nodes;
+
+  for (uint32_t i = 0; i < policy()->kMinTabsToLoad + 1; i++) {
+    page_nodes.push_back(CreateNode<performance_manager::PageNodeImpl>());
+    raw_page_nodes.push_back(page_nodes.back().get());
+  }
+
+  // When free memory limit is reached.
+  const size_t kFreeMemoryLimit = policy()->kDesiredAmountOfFreeMemoryMb - 1;
+  policy()->SetFreeMemoryForTesting(kFreeMemoryLimit);
+
+  // Test that the minimum number of tabs to load is respected.
+  for (uint32_t i = 0; i < policy()->kMinTabsToLoad; i++) {
+    EXPECT_TRUE(policy()->ShouldLoad(raw_page_nodes[i]));
+    policy()->tab_loads_started_++;
+  }
+  EXPECT_FALSE(policy()->ShouldLoad(raw_page_nodes[policy()->kMinTabsToLoad]));
+}
+
+TEST_F(BackgroundTabLoadingPolicyTest, ShouldLoad_FreeMemory) {
+  // Create a PageNode to restore.
+  performance_manager::TestNodeWrapper<performance_manager::PageNodeImpl>
+      page_node;
+  PageNode* raw_page_node;
+
+  page_node = CreateNode<performance_manager::PageNodeImpl>();
+  raw_page_node = page_node.get();
+
+  // Simulate that kMinTabsToLoad have loaded.
+  policy()->tab_loads_started_ = policy()->kMinTabsToLoad;
+
+  // Test the free memory constraint.
+  const size_t kFreeMemoryLimit = policy()->kDesiredAmountOfFreeMemoryMb;
+  policy()->SetFreeMemoryForTesting(kFreeMemoryLimit);
+  EXPECT_TRUE(policy()->ShouldLoad(raw_page_node));
+  policy()->SetFreeMemoryForTesting(kFreeMemoryLimit - 1);
+  EXPECT_FALSE(policy()->ShouldLoad(raw_page_node));
+  policy()->SetFreeMemoryForTesting(kFreeMemoryLimit + 1);
+  EXPECT_TRUE(policy()->ShouldLoad(raw_page_node));
+}
+
+TEST_F(BackgroundTabLoadingPolicyTest, ShouldLoad_OldTab) {
+  // Create an old tab.
+  performance_manager::TestNodeWrapper<performance_manager::PageNodeImpl>
+      page_node;
+  PageNode* raw_page_node;
+
+  page_node = CreateNode<performance_manager::PageNodeImpl>(
+      WebContentsProxy(), std::string(), GURL(), false, false,
+      base::TimeTicks::Now() - (base::TimeDelta::FromSeconds(1) +
+                                policy()->kMaxTimeSinceLastUseToLoad));
+  raw_page_node = page_node.get();
+
+  // Simulate that kMinTabsToLoad have loaded.
+  policy()->tab_loads_started_ = policy()->kMinTabsToLoad;
+
+  // Test the max time since last use threshold.
+  EXPECT_FALSE(policy()->ShouldLoad(raw_page_node));
+}
+
 }  // namespace policies
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/DEPS b/chrome/browser/performance_manager/persistence/site_data/DEPS
deleted file mode 100644
index 888bc16..0000000
--- a/chrome/browser/performance_manager/persistence/site_data/DEPS
+++ /dev/null
@@ -1,7 +0,0 @@
-specific_include_rules = {
-  # This file is the glue between a Profile and the local site data store, it's
-  # browser specific and needs to be able to use some browser parts.
-  "site_data_cache_facade.*\.cc": [
-      "+chrome/browser",
-  ]
-}
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_facade.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_facade.cc
index 1741b527..db48332 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_facade.cc
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_facade.cc
@@ -11,10 +11,10 @@
 #include "base/bind.h"
 #include "base/run_loop.h"
 #include "chrome/browser/history/history_service_factory.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_factory.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_impl.h"
 #include "components/performance_manager/public/performance_manager.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_facade_unittest.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_facade_unittest.cc
index 8930699e..6509529 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_facade_unittest.cc
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_facade_unittest.cc
@@ -15,13 +15,14 @@
 #include "base/run_loop.h"
 #include "base/task/post_task.h"
 #include "base/test/bind_test_util.h"
-#include "chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h"
 #include "chrome/browser/performance_manager/persistence/site_data/unittest_utils.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/performance_manager/performance_manager_impl.h"
+#include "components/performance_manager/persistence/site_data/leveldb_site_data_store.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_factory.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_impl.h"
 #include "content/public/test/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/tab_visibility.h b/chrome/browser/performance_manager/persistence/site_data/tab_visibility.h
deleted file mode 100644
index 81c653e3..0000000
--- a/chrome/browser/performance_manager/persistence/site_data/tab_visibility.h
+++ /dev/null
@@ -1,14 +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 CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_TAB_VISIBILITY_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_TAB_VISIBILITY_H_
-
-namespace performance_manager {
-
-enum class TabVisibility { kBackground, kForeground };
-
-}  // namespace performance_manager
-
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_TAB_VISIBILITY_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/unittest_utils.cc b/chrome/browser/performance_manager/persistence/site_data/unittest_utils.cc
index 3caaee3..f57a40c 100644
--- a/chrome/browser/performance_manager/persistence/site_data/unittest_utils.cc
+++ b/chrome/browser/performance_manager/persistence/site_data/unittest_utils.cc
@@ -6,44 +6,11 @@
 
 #include <utility>
 
-#include "base/bind_helpers.h"
 #include "base/callback.h"
 
 namespace performance_manager {
 namespace testing {
 
-MockSiteDataImplOnDestroyDelegate::MockSiteDataImplOnDestroyDelegate() =
-    default;
-MockSiteDataImplOnDestroyDelegate::~MockSiteDataImplOnDestroyDelegate() =
-    default;
-
-NoopSiteDataStore::NoopSiteDataStore() = default;
-NoopSiteDataStore::~NoopSiteDataStore() = default;
-
-void NoopSiteDataStore::ReadSiteDataFromStore(
-    const url::Origin& origin,
-    ReadSiteDataFromStoreCallback callback) {
-  std::move(callback).Run(base::nullopt);
-}
-
-void NoopSiteDataStore::WriteSiteDataIntoStore(
-    const url::Origin& origin,
-    const SiteDataProto& site_characteristic_proto) {}
-
-void NoopSiteDataStore::RemoveSiteDataFromStore(
-    const std::vector<url::Origin>& site_origins) {}
-
-void NoopSiteDataStore::ClearStore() {}
-
-void NoopSiteDataStore::GetStoreSize(GetStoreSizeCallback callback) {
-  std::move(callback).Run(base::nullopt, base::nullopt);
-}
-
-void NoopSiteDataStore::SetInitializationCallbackForTesting(
-    base::OnceClosure callback) {
-  std::move(callback).Run();
-}
-
 TestWithPerformanceManager::TestWithPerformanceManager() = default;
 
 TestWithPerformanceManager::~TestWithPerformanceManager() = default;
diff --git a/chrome/browser/performance_manager/persistence/site_data/unittest_utils.h b/chrome/browser/performance_manager/persistence/site_data/unittest_utils.h
index ea1b808..2c7fd31 100644
--- a/chrome/browser/performance_manager/persistence/site_data/unittest_utils.h
+++ b/chrome/browser/performance_manager/persistence/site_data/unittest_utils.h
@@ -6,53 +6,15 @@
 #define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_UNITTEST_UTILS_H_
 
 #include <memory>
-#include <vector>
 
 #include "base/macros.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_store.h"
 #include "components/performance_manager/performance_manager_impl.h"
 #include "content/public/test/browser_task_environment.h"
-#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace performance_manager {
 namespace testing {
 
-class MockSiteDataImplOnDestroyDelegate
-    : public internal::SiteDataImpl::OnDestroyDelegate {
- public:
-  MockSiteDataImplOnDestroyDelegate();
-  ~MockSiteDataImplOnDestroyDelegate();
-
-  MOCK_METHOD1(OnSiteDataImplDestroyed, void(internal::SiteDataImpl*));
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(MockSiteDataImplOnDestroyDelegate);
-};
-
-// An implementation of a SiteDataStore that doesn't record anything.
-class NoopSiteDataStore : public SiteDataStore {
- public:
-  NoopSiteDataStore();
-  ~NoopSiteDataStore() override;
-
-  // SiteDataStore:
-  void ReadSiteDataFromStore(const url::Origin& origin,
-                             ReadSiteDataFromStoreCallback callback) override;
-  void WriteSiteDataIntoStore(
-      const url::Origin& origin,
-      const SiteDataProto& site_characteristic_proto) override;
-  void RemoveSiteDataFromStore(
-      const std::vector<url::Origin>& site_origins) override;
-  void ClearStore() override;
-  void GetStoreSize(GetStoreSizeCallback callback) override;
-  void SetInitializationCallbackForTesting(base::OnceClosure callback) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(NoopSiteDataStore);
-};
-
 class TestWithPerformanceManager : public ::testing::Test {
  public:
   TestWithPerformanceManager();
diff --git a/chrome/browser/policy/BUILD.gn b/chrome/browser/policy/BUILD.gn
index 87450f3..ebc82e7 100644
--- a/chrome/browser/policy/BUILD.gn
+++ b/chrome/browser/policy/BUILD.gn
@@ -19,6 +19,12 @@
   ]
 
   if (is_win) {
+    deps += [ "//chrome/install_static:install_static_util" ]
+
     libs = [ "wtsapi32.lib" ]
   }
+
+  if (is_mac) {
+    deps += [ "//build:branding_buildflags" ]
+  }
 }
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 3843321..2362f304 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -750,6 +750,9 @@
   { key::kShowAccessibilityOptionsInSystemTrayMenu,
     ash::prefs::kShouldAlwaysShowAccessibilityMenu,
     base::Value::Type::BOOLEAN },
+  { key::kFloatingAccessibilityMenuEnabled,
+    ash::prefs::kAccessibilityFloatingMenuEnabled,
+    base::Value::Type::BOOLEAN},
   { key::kLargeCursorEnabled,
     ash::prefs::kAccessibilityLargeCursorEnabled,
     base::Value::Type::BOOLEAN },
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 933847b..57d2ed7a 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -46,6 +46,7 @@
 #include "chrome/browser/media_galleries/media_galleries_preferences_factory.h"
 #include "chrome/browser/notifications/notifier_state_tracker_factory.h"
 #include "chrome/browser/ntp_snippets/content_suggestions_service_factory.h"
+#include "chrome/browser/page_load_metrics/observers/https_engagement_metrics/https_engagement_service_factory.h"
 #include "chrome/browser/password_manager/password_store_factory.h"
 #include "chrome/browser/permissions/adaptive_quiet_notification_permission_ui_enabler.h"
 #include "chrome/browser/plugins/plugin_prefs_factory.h"
@@ -53,6 +54,7 @@
 #include "chrome/browser/predictors/autocomplete_action_predictor_factory.h"
 #include "chrome/browser/predictors/loading_predictor_factory.h"
 #include "chrome/browser/predictors/predictor_database_factory.h"
+#include "chrome/browser/prefs/pref_metrics_service.h"
 #include "chrome/browser/prerender/prerender_link_manager_factory.h"
 #include "chrome/browser/prerender/prerender_manager_factory.h"
 #include "chrome/browser/prerender/prerender_message_filter.h"
@@ -265,6 +267,7 @@
   HistoryServiceFactory::GetInstance();
   HistoryUiFaviconRequestHandlerFactory::GetInstance();
   HostContentSettingsMapFactory::GetInstance();
+  HttpsEngagementServiceFactory::GetInstance();
   IdentityManagerFactory::EnsureFactoryAndDependeeFactoriesBuilt();
   InMemoryURLIndexFactory::GetInstance();
 #if !defined(OS_ANDROID)
@@ -305,6 +308,7 @@
 #if BUILDFLAG(ENABLE_PLUGINS)
   PluginPrefsFactory::GetInstance();
 #endif
+  PrefMetricsService::Factory::GetInstance();
   PrefsTabHelper::GetServiceInstance();
   policy::UserCloudPolicyInvalidatorFactory::GetInstance();
 #if !defined(OS_CHROMEOS)
diff --git a/chrome/browser/recovery/recovery_install_global_error.cc b/chrome/browser/recovery/recovery_install_global_error.cc
index 44a76f76..9e9d6bfe 100644
--- a/chrome/browser/recovery/recovery_install_global_error.cc
+++ b/chrome/browser/recovery/recovery_install_global_error.cc
@@ -19,8 +19,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/prefs/pref_service.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/image/image.h"
-#include "ui/gfx/paint_vector_icon.h"
+#include "ui/base/models/image_model.h"
 #include "ui/native_theme/native_theme.h"
 
 RecoveryInstallGlobalError::RecoveryInstallGlobalError(Profile* profile)
@@ -68,11 +67,9 @@
   return l10n_util::GetStringUTF16(IDS_UPDATE_NOW);
 }
 
-gfx::Image RecoveryInstallGlobalError::MenuItemIcon() {
-  return gfx::Image(gfx::CreateVectorIcon(
-      kBrowserToolsUpdateIcon,
-      ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
-          ui::NativeTheme::kColorId_AlertSeverityHigh)));
+ui::ImageModel RecoveryInstallGlobalError::MenuItemIcon() {
+  return ui::ImageModel::FromVectorIcon(
+      kBrowserToolsUpdateIcon, ui::NativeTheme::kColorId_AlertSeverityHigh);
 }
 
 void RecoveryInstallGlobalError::ExecuteMenuItem(Browser* browser) {
diff --git a/chrome/browser/recovery/recovery_install_global_error.h b/chrome/browser/recovery/recovery_install_global_error.h
index 42172d8..eb31abe 100644
--- a/chrome/browser/recovery/recovery_install_global_error.h
+++ b/chrome/browser/recovery/recovery_install_global_error.h
@@ -31,7 +31,7 @@
   bool HasMenuItem() override;
   int MenuItemCommandID() override;
   base::string16 MenuItemLabel() override;
-  gfx::Image MenuItemIcon() override;
+  ui::ImageModel MenuItemIcon() override;
   void ExecuteMenuItem(Browser* browser) override;
   bool HasBubbleView() override;
   bool HasShownBubbleView() override;
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 5652fbd..a7e0792c 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -155,6 +155,7 @@
 #include "ui/base/clipboard/scoped_clipboard_writer.h"
 #include "ui/base/emoji/emoji_panel_helper.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/favicon_size.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/text_elider.h"
@@ -525,7 +526,8 @@
   gfx::Image sized_icon = profiles::GetSizedAvatarIcon(
       icon, true /* is_rectangle */, target_dip_width, target_dip_height,
       profiles::SHAPE_CIRCLE);
-  menu->SetIcon(menu->GetItemCount() - 1, sized_icon);
+  menu->SetIcon(menu->GetItemCount() - 1,
+                ui::ImageModel::FromImage(sized_icon));
 }
 #endif  // !defined(OS_CHROMEOS)
 
@@ -1261,7 +1263,7 @@
                 IDS_LINK_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
                 send_tab_to_self::GetSingleTargetDeviceName(
                     GetBrowser()->profile())),
-            kSendTabToSelfIcon);
+            ui::ImageModel::FromVectorIcon(kSendTabToSelfIcon));
 #endif
         send_tab_to_self::RecordSendTabToSelfClickResult(
             send_tab_to_self::kLinkMenu,
@@ -1281,7 +1283,8 @@
 #else
         menu_model_.AddSubMenuWithStringIdAndIcon(
             IDC_CONTENT_LINK_SEND_TAB_TO_SELF, IDS_LINK_MENU_SEND_TAB_TO_SELF,
-            send_tab_to_self_sub_menu_model_.get(), kSendTabToSelfIcon);
+            send_tab_to_self_sub_menu_model_.get(),
+            ui::ImageModel::FromVectorIcon(kSendTabToSelfIcon));
 #endif
       }
     }
@@ -1367,7 +1370,8 @@
   MenuManager* menu_manager = MenuManager::Get(browser_context_);
   // TODO(crbug.com/1052707): Use AppIconManager to read PWA icons.
   gfx::Image icon = menu_manager->GetIconForExtension(*app_id);
-  menu_model_.SetIcon(menu_model_.GetItemCount() - 1, icon);
+  menu_model_.SetIcon(menu_model_.GetItemCount() - 1,
+                      ui::ImageModel::FromImage(icon));
 }
 
 void RenderViewContextMenu::AppendImageItems() {
@@ -1501,7 +1505,7 @@
               IDS_CONTEXT_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
               send_tab_to_self::GetSingleTargetDeviceName(
                   GetBrowser()->profile())),
-          kSendTabToSelfIcon);
+          ui::ImageModel::FromVectorIcon(kSendTabToSelfIcon));
 #endif
       send_tab_to_self::RecordSendTabToSelfClickResult(
           send_tab_to_self::kContentMenu,
@@ -1520,7 +1524,8 @@
 #else
       menu_model_.AddSubMenuWithStringIdAndIcon(
           IDC_SEND_TAB_TO_SELF, IDS_CONTEXT_MENU_SEND_TAB_TO_SELF,
-          send_tab_to_self_sub_menu_model_.get(), kSendTabToSelfIcon);
+          send_tab_to_self_sub_menu_model_.get(),
+          ui::ImageModel::FromVectorIcon(kSendTabToSelfIcon));
 #endif
     }
   }
@@ -1624,13 +1629,9 @@
                      base::ASCIIToUTF16(" "), &params_.selection_text);
 
   AutocompleteMatch match;
-  AutocompleteClassifierFactory::GetForProfile(GetProfile())->Classify(
-      params_.selection_text,
-      false,
-      false,
-      metrics::OmniboxEventProto::INVALID_SPEC,
-      &match,
-      NULL);
+  AutocompleteClassifierFactory::GetForProfile(GetProfile())
+      ->Classify(params_.selection_text, false, false,
+                 metrics::OmniboxEventProto::INVALID_SPEC, &match, nullptr);
   selection_navigation_url_ = match.destination_url;
   if (!selection_navigation_url_.is_valid())
     return;
diff --git a/chrome/browser/resource_coordinator/DEPS b/chrome/browser/resource_coordinator/DEPS
index 1602685..90c3ba91 100644
--- a/chrome/browser/resource_coordinator/DEPS
+++ b/chrome/browser/resource_coordinator/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   # resource_coordinator can include files from performance_manager.
   "+chrome/browser/performance_manager",
+  "+components/performance_manager",
 
   "+services/resource_coordinator/public",
   # No inclusion of blink from the browser, other than strictly enum/POD,
diff --git a/chrome/browser/resource_coordinator/leveldb_site_characteristics_database_unittest.cc b/chrome/browser/resource_coordinator/leveldb_site_characteristics_database_unittest.cc
index 1b1ea740..35534665 100644
--- a/chrome/browser/resource_coordinator/leveldb_site_characteristics_database_unittest.cc
+++ b/chrome/browser/resource_coordinator/leveldb_site_characteristics_database_unittest.cc
@@ -16,7 +16,7 @@
 #include "base/test/task_environment.h"
 #include "base/test/test_file_util.h"
 #include "build/build_config.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data.pb.h"
+#include "components/performance_manager/persistence/site_data/site_data.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/leveldatabase/leveldb_chrome.h"
 #include "url/gurl.h"
diff --git a/chrome/browser/resource_coordinator/local_site_characteristics_data_impl.h b/chrome/browser/resource_coordinator/local_site_characteristics_data_impl.h
index 23d8872..d80f8d2 100644
--- a/chrome/browser/resource_coordinator/local_site_characteristics_data_impl.h
+++ b/chrome/browser/resource_coordinator/local_site_characteristics_data_impl.h
@@ -12,12 +12,12 @@
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
-#include "chrome/browser/performance_manager/persistence/site_data/exponential_moving_average.h"
-#include "chrome/browser/performance_manager/persistence/site_data/feature_usage.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data.pb.h"
-#include "chrome/browser/performance_manager/persistence/site_data/tab_visibility.h"
 #include "chrome/browser/resource_coordinator/local_site_characteristics_database.h"
 #include "chrome/browser/resource_coordinator/tab_manager_features.h"
+#include "components/performance_manager/persistence/site_data/exponential_moving_average.h"
+#include "components/performance_manager/persistence/site_data/feature_usage.h"
+#include "components/performance_manager/persistence/site_data/site_data.pb.h"
+#include "components/performance_manager/persistence/site_data/tab_visibility.h"
 #include "url/origin.h"
 
 namespace resource_coordinator {
diff --git a/chrome/browser/resource_coordinator/local_site_characteristics_data_impl_unittest.cc b/chrome/browser/resource_coordinator/local_site_characteristics_data_impl_unittest.cc
index 2ed22d1..6da9851 100644
--- a/chrome/browser/resource_coordinator/local_site_characteristics_data_impl_unittest.cc
+++ b/chrome/browser/resource_coordinator/local_site_characteristics_data_impl_unittest.cc
@@ -8,9 +8,9 @@
 #include "base/memory/ref_counted.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/simple_test_tick_clock.h"
-#include "chrome/browser/performance_manager/persistence/site_data/feature_usage.h"
 #include "chrome/browser/resource_coordinator/local_site_characteristics_data_unittest_utils.h"
 #include "chrome/browser/resource_coordinator/time.h"
+#include "components/performance_manager/persistence/site_data/feature_usage.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
diff --git a/chrome/browser/resource_coordinator/local_site_characteristics_data_store_inspector.h b/chrome/browser/resource_coordinator/local_site_characteristics_data_store_inspector.h
index 8ccb9ae0..812e815f 100644
--- a/chrome/browser/resource_coordinator/local_site_characteristics_data_store_inspector.h
+++ b/chrome/browser/resource_coordinator/local_site_characteristics_data_store_inspector.h
@@ -12,7 +12,7 @@
 #include "base/callback.h"
 #include "base/optional.h"
 #include "base/supports_user_data.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data.pb.h"
+#include "components/performance_manager/persistence/site_data/site_data.pb.h"
 #include "url/origin.h"
 
 class Profile;
diff --git a/chrome/browser/resource_coordinator/local_site_characteristics_data_writer_unittest.cc b/chrome/browser/resource_coordinator/local_site_characteristics_data_writer_unittest.cc
index 20efa96..aabae6e8 100644
--- a/chrome/browser/resource_coordinator/local_site_characteristics_data_writer_unittest.cc
+++ b/chrome/browser/resource_coordinator/local_site_characteristics_data_writer_unittest.cc
@@ -7,9 +7,9 @@
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/time/time.h"
-#include "chrome/browser/performance_manager/persistence/site_data/feature_usage.h"
 #include "chrome/browser/resource_coordinator/local_site_characteristics_data_impl.h"
 #include "chrome/browser/resource_coordinator/local_site_characteristics_data_unittest_utils.h"
+#include "components/performance_manager/persistence/site_data/feature_usage.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
diff --git a/chrome/browser/resource_coordinator/local_site_characteristics_database.h b/chrome/browser/resource_coordinator/local_site_characteristics_database.h
index 51124b5e..2d8cd25 100644
--- a/chrome/browser/resource_coordinator/local_site_characteristics_database.h
+++ b/chrome/browser/resource_coordinator/local_site_characteristics_database.h
@@ -10,7 +10,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/optional.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data.pb.h"
+#include "components/performance_manager/persistence/site_data/site_data.pb.h"
 #include "url/origin.h"
 
 namespace resource_coordinator {
diff --git a/chrome/browser/resource_coordinator/site_characteristics_data_reader.h b/chrome/browser/resource_coordinator/site_characteristics_data_reader.h
index 6c39615b..6f0c4868 100644
--- a/chrome/browser/resource_coordinator/site_characteristics_data_reader.h
+++ b/chrome/browser/resource_coordinator/site_characteristics_data_reader.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_RESOURCE_COORDINATOR_SITE_CHARACTERISTICS_DATA_READER_H_
 #define CHROME_BROWSER_RESOURCE_COORDINATOR_SITE_CHARACTERISTICS_DATA_READER_H_
 
-#include "chrome/browser/performance_manager/persistence/site_data/feature_usage.h"
+#include "components/performance_manager/persistence/site_data/feature_usage.h"
 
 #include "base/callback_forward.h"
 
diff --git a/chrome/browser/resource_coordinator/site_characteristics_data_store.h b/chrome/browser/resource_coordinator/site_characteristics_data_store.h
index 2345fb8c..59037e23c 100644
--- a/chrome/browser/resource_coordinator/site_characteristics_data_store.h
+++ b/chrome/browser/resource_coordinator/site_characteristics_data_store.h
@@ -8,10 +8,10 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "chrome/browser/performance_manager/persistence/site_data/tab_visibility.h"
 #include "chrome/browser/resource_coordinator/site_characteristics_data_reader.h"
 #include "chrome/browser/resource_coordinator/site_characteristics_data_writer.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/performance_manager/persistence/site_data/tab_visibility.h"
 #include "url/origin.h"
 
 namespace resource_coordinator {
diff --git a/chrome/browser/resource_coordinator/site_characteristics_data_writer.h b/chrome/browser/resource_coordinator/site_characteristics_data_writer.h
index 7c2af123..cbfff1c 100644
--- a/chrome/browser/resource_coordinator/site_characteristics_data_writer.h
+++ b/chrome/browser/resource_coordinator/site_characteristics_data_writer.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_RESOURCE_COORDINATOR_SITE_CHARACTERISTICS_DATA_WRITER_H_
 
 #include "base/time/time.h"
-#include "chrome/browser/performance_manager/persistence/site_data/tab_visibility.h"
+#include "components/performance_manager/persistence/site_data/tab_visibility.h"
 
 namespace resource_coordinator {
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index 6377d719..786f085 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -399,6 +399,7 @@
       "background/locale_output_helper_test.js",
       "background/logging/log_store_test.js",
       "background/output_test.js",
+      "background/portals_test.js",
       "background/recovery_strategy_test.js",
       "background/smart_sticky_mode_test.js",
       "braille/braille_table_test.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
index a300e369..7f6edf73 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
@@ -110,6 +110,8 @@
       } else {
         chrome.accessibilityPrivate.setKeyboardListener(true, false);
       }
+      CommandHandler.smartStickyMode_.onStickyModeCommand(
+          ChromeVoxState.instance.currentRange);
       return false;
     case 'passThroughMode':
       ChromeVox.passThroughMode = true;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/portals_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/portals_test.js
new file mode 100644
index 0000000..b26aebf
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/portals_test.js
@@ -0,0 +1,84 @@
+// 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 test fixture.
+GEN_INCLUDE(['../testing/chromevox_next_e2e_test_base.js']);
+
+/**
+ * Test fixture for Portals.
+ */
+ChromeVoxPortalsTest = class extends ChromeVoxNextE2ETest {
+  /** @override */
+  setUp() {
+    window.EventType = chrome.automation.EventType;
+    window.RoleType = chrome.automation.RoleType;
+    window.doCmd = this.doCmd;
+  }
+
+  /** @override */
+  testGenCppIncludes() {
+    super.testGenCppIncludes();
+    GEN(`#include "third_party/blink/public/common/features.h"`);
+  }
+
+  /** @override */
+  get featureList() {
+    return {enabled: ['blink::features::kPortals']};
+  }
+
+  get testServer() {
+    return true;
+  }
+
+  /**
+   * Create a function which perform the command |cmd|.
+   * @param {string} cmd
+   * @return {function() : void}
+   */
+  doCmd(cmd) {
+    return function() {
+      CommandHandler.onCommand(cmd);
+    };
+  }
+};
+
+TEST_F('ChromeVoxPortalsTest', 'ShouldFocusPortal', function() {
+  this.runWithLoadedTree(
+      undefined,
+      function(root) {
+        const portal = root.find({role: RoleType.PORTAL});
+        const button = root.find({role: RoleType.BUTTON});
+        assertEquals(RoleType.PORTAL, portal.role);
+        assertEquals(RoleType.BUTTON, button.role);
+
+        const afterPortalIsReady = this.newCallback(() => {
+          const chromeVoxState = ChromeVoxState.instance;
+          portal.addEventListener(EventType.FOCUS, this.newCallback(function() {
+            assertEquals(portal, chromeVoxState.currentRange.start.node);
+            // test is done.
+          }));
+          assertEquals(button, chromeVoxState.currentRange.start.node);
+          doCmd('nextObject')();
+        });
+
+        const onChildrenChanged = evt => {
+          if (portal.children.length) {
+            portal.removeEventListener(
+                EventType.CHILDREN_CHANGED, onChildrenChanged, true);
+            afterPortalIsReady();
+          }
+        };
+
+        button.focus();
+        button.addEventListener(EventType.FOCUS, this.newCallback(function() {
+          if (!portal.children.length) {
+            portal.addEventListener(
+                EventType.CHILDREN_CHANGED, onChildrenChanged, true);
+            return;
+          }
+          afterPortalIsReady();
+        }));
+      }.bind(this),
+      `${testRunnerParams.testServerBaseUrl}portal/portal-and-button.html`);
+});
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js
index 0e3c7ead..0132a07 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js
@@ -10,6 +10,7 @@
 
 goog.provide('SmartStickyMode');
 
+goog.require('AutomationUtil');
 goog.require('ChromeVoxState');
 
 /** @implements {ChromeVoxStateObserver} */
@@ -24,6 +25,8 @@
      * @private {boolean}
      */
     this.didTurnOffStickyMode_ = false;
+    /** @private {chrome.automation.AutomationNode|undefined} */
+    this.ignoredNodeSubtree_;
 
     ChromeVoxState.addObserver(this);
   }
@@ -35,33 +38,24 @@
       return;
     }
 
+    const node = newRange.start.node;
+    if (this.ignoredNodeSubtree_) {
+      const editableOrRelatedEditable =
+          this.getEditableOrRelatedEditable_(node);
+      if (editableOrRelatedEditable &&
+          AutomationUtil.isDescendantOf(
+              editableOrRelatedEditable, this.ignoredNodeSubtree_)) {
+        return;
+      }
+
+      // Clear it when the user leaves the subtree.
+      this.ignoredNodeSubtree_ = undefined;
+    }
+
     // Several cases arise which may lead to a sticky mode toggle:
     // The node is either editable itself or a descendant of an editable.
     // The node is a relation target of an editable.
-    const node = newRange.start.node;
-    let shouldTurnOffStickyMode = false;
-    if (node.state[chrome.automation.StateType.EDITABLE] ||
-        (node.parent &&
-         node.parent.state[chrome.automation.StateType.EDITABLE])) {
-      // This covers both editable nodes, and inline text boxes (which are not
-      // editable themselves, but may have an editable parent).
-      shouldTurnOffStickyMode = true;
-    } else {
-      let focus = node;
-      while (!shouldTurnOffStickyMode && focus) {
-        if (focus.activeDescendantFor && focus.activeDescendantFor.length) {
-          shouldTurnOffStickyMode |= focus.activeDescendantFor.some(
-              (n) => n.state[chrome.automation.StateType.EDITABLE]);
-        }
-
-        if (focus.controlledBy && focus.controlledBy.length) {
-          shouldTurnOffStickyMode |= focus.controlledBy.some(
-              (n) => n.state[chrome.automation.StateType.EDITABLE]);
-        }
-
-        focus = focus.parent;
-      }
-    }
+    const shouldTurnOffStickyMode = !!this.getEditableOrRelatedEditable_(node);
 
     // This toggler should not make any changes when the range isn't what we're
     // lloking for and we haven't previously tracked any sticky mode state from
@@ -110,4 +104,82 @@
   stopIgnoringRangeChanges() {
     this.ignoreRangeChanges_ = false;
   }
+
+  /**
+   * Called whenever a user toggles sticky mode. In this case, we need to ensure
+   * we reset our internal state appropriately.
+   * @param {!cursors.Range} range The range when the sticky mode command was
+   *     received.
+   */
+  onStickyModeCommand(range) {
+    if (!this.didTurnOffStickyMode_) {
+      return;
+    }
+
+    this.didTurnOffStickyMode_ = false;
+
+    // Resetting the above isn't quite enough. We now have to track the current
+    // range, if it is editable or has an editable relation, to ensure we don't
+    // interfere with the user's sticky mode state.
+    if (!range || !range.start) {
+      return;
+    }
+
+    let editable = this.getEditableOrRelatedEditable_(range.start.node);
+    if (!editable) {
+      return;
+    }
+
+    while (!editable.editableRoot) {
+      editable = editable.parent;
+    }
+    this.ignoredNodeSubtree_ = editable;
+  }
+
+  /**
+   * @param {chrome.automation.AutomationNode} node
+   * @return {chrome.automation.AutomationNode}
+   * @private
+   */
+  getEditableOrRelatedEditable_(node) {
+    if (!node) {
+      return null;
+    }
+
+    if (node.state[chrome.automation.StateType.EDITABLE]) {
+      return node;
+    } else if (
+        node.parent &&
+        node.parent.state[chrome.automation.StateType.EDITABLE]) {
+      // This covers inline text boxes (which are not
+      // editable themselves, but may have an editable parent).
+      return node.parent;
+    } else {
+      let focus = node;
+      let found;
+      while (!found && focus) {
+        if (focus.activeDescendantFor && focus.activeDescendantFor.length) {
+          found = focus.activeDescendantFor.find(
+              (n) => n.state[chrome.automation.StateType.EDITABLE]);
+        }
+
+        if (found) {
+          return found;
+        }
+
+        if (focus.controlledBy && focus.controlledBy.length) {
+          found = focus.controlledBy.find(
+              (n) => n.state[chrome.automation.StateType.EDITABLE]);
+        }
+
+        if (found) {
+          return found;
+        }
+
+        focus = focus.parent;
+      }
+    }
+
+    return null;
+  }
 };
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode_test.js
index 281eb5c..e708f0c9 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode_test.js
@@ -10,54 +10,103 @@
 /**
  * Test fixture for SmartStickyMode.
  */
-ChromeVoxSmartStickyModeTest = class extends ChromeVoxNextE2ETest {};
+ChromeVoxSmartStickyModeTest = class extends ChromeVoxNextE2ETest {
+  /** @override */
+  setUp() {
+    this.ssm_ = new SmartStickyMode();
+    // Deregister from actual range changes.
+    ChromeVoxState.removeObserver(this.ssm_);
+    assertFalse(ssm.didTurnOffStickyMode_);
+  }
+
+  assertDidTurnOffForNode(node) {
+    this.ssm_.onCurrentRangeChanged(cursors.Range.fromNode(node));
+    assertTrue(this.ssm_.didTurnOffStickyMode_);
+  }
+
+  assertDidNotTurnOffForNode(node) {
+    this.ssm_.onCurrentRangeChanged(cursors.Range.fromNode(node));
+    assertFalse(this.ssm_.didTurnOffStickyMode_);
+  }
+
+  get relationsDoc() {
+    return `
+      <p>start</p>
+      <input aria-controls="controls-target" type="text"></input>
+      <textarea aria-activedescendant="active-descendant-target"></textarea>
+      <div contenteditable><h3>hello</h3></div>
+      <ul id="controls-target"><li>end</ul>
+      <ul id="active-descendant-target"><li>end</ul>
+    `;
+  }
+};
 
 TEST_F('ChromeVoxSmartStickyModeTest', 'PossibleRangeTypes', function() {
-  this.runWithLoadedTree(
-      `
-    <p>start</p>
-    <input aria-controls="controls-target" type="text"></input>
-    <textarea aria-activedescendant="active-descendant-target"></textarea>
-    <div contenteditable><h3>hello</h3></div>
-    <ul id="controls-target"><li>end</ul>
-    <ul id="active-descendant-target"><li>end</ul>
-  `,
-      function(root) {
-        const ssm = new SmartStickyMode();
+  this.runWithLoadedTree(this.relationsDoc, function(root) {
+    const [p, input, textarea, contenteditable, ul1, ul2] = root.children;
 
-        // Deregister from actual range changes.
-        ChromeVoxState.removeObserver(ssm);
-        assertFalse(ssm.didTurnOffStickyMode_);
+    // First, turn on sticky mode and try changing range to various parts of
+    // the document.
+    ChromeVoxBackground.setPref(
+        'sticky', true /* value */, true /* announce */);
+    this.assertDidTurnOffForNode(input);
+    this.assertDidTurnOffForNode(textarea);
+    this.assertDidNotTurnOffForNode(p);
+    this.assertDidTurnOffForNode(contenteditable);
+    this.assertDidTurnOffForNode(ul1);
+    this.assertDidNotTurnOffForNode(p);
+    this.assertDidTurnOffForNode(ul2);
+    this.assertDidTurnOffForNode(ul1.firstChild);
+    this.assertDidNotTurnOffForNode(ul1.parent);
+    this.assertDidNotTurnOffForNode(ul2.parent);
+    this.assertDidNotTurnOffForNode(p);
+    this.assertDidTurnOffForNode(ul2.firstChild);
+    this.assertDidNotTurnOffForNode(p);
+    this.assertDidNotTurnOffForNode(contenteditable.parent);
+    this.assertDidTurnOffForNode(contenteditable.find({role: 'heading'}));
+    this.assertDidTurnOffForNode(contenteditable.find({role: 'inlineTextBox'}));
+  });
+});
+
+TEST_F(
+    'ChromeVoxSmartStickyModeTest', 'UserPressesStickyModeCommand', function() {
+      this.runWithLoadedTree(this.relationsDoc, function(root) {
         const [p, input, textarea, contenteditable, ul1, ul2] = root.children;
-
-        function assertDidTurnOffForNode(node) {
-          ssm.onCurrentRangeChanged(cursors.Range.fromNode(node));
-          assertTrue(ssm.didTurnOffStickyMode_);
-        }
-
-        function assertDidNotTurnOffForNode(node) {
-          ssm.onCurrentRangeChanged(cursors.Range.fromNode(node));
-          assertFalse(ssm.didTurnOffStickyMode_);
-        }
-
-        // First, turn on sticky mode and try changing range to various parts of
-        // the document.
         ChromeVoxBackground.setPref(
             'sticky', true /* value */, true /* announce */);
 
-        assertDidTurnOffForNode(input);
-        assertDidTurnOffForNode(textarea);
-        assertDidNotTurnOffForNode(p);
+        // Mix in calls to turn on / off sticky mode while moving the range
+        // around.
+        this.assertDidTurnOffForNode(input);
+        this.ssm_.onStickyModeCommand(cursors.Range.fromNode(input));
+        this.assertDidNotTurnOffForNode(input);
+        this.ssm_.onStickyModeCommand(cursors.Range.fromNode(input));
+        this.assertDidNotTurnOffForNode(input);
+        this.assertDidNotTurnOffForNode(input.firstChild);
+        this.assertDidNotTurnOffForNode(p);
 
-        assertDidTurnOffForNode(contenteditable);
-        assertDidTurnOffForNode(ul1);
-        assertDidNotTurnOffForNode(p);
-        assertDidTurnOffForNode(ul2);
-        assertDidTurnOffForNode(ul1.firstChild);
-        assertDidNotTurnOffForNode(p);
-        assertDidTurnOffForNode(ul2.firstChild);
-        assertDidNotTurnOffForNode(p);
-        assertDidTurnOffForNode(contenteditable.find({role: 'heading'}));
-        assertDidTurnOffForNode(contenteditable.find({role: 'inlineTextBox'}));
+        // Make sure sticky mode is on again. This call doesn't impact our
+        // instance of SmartStickyMode.
+        ChromeVoxBackground.setPref(
+            'sticky', true /* value */, true /* announce */);
+
+        // Mix in more sticky mode user commands and move to related nodes.
+        this.assertDidTurnOffForNode(contenteditable);
+        this.assertDidTurnOffForNode(ul2);
+        this.ssm_.onStickyModeCommand(cursors.Range.fromNode(ul2));
+        this.assertDidNotTurnOffForNode(ul2);
+        this.assertDidNotTurnOffForNode(ul2.firstChild);
+        this.assertDidNotTurnOffForNode(contenteditable);
+        this.ssm_.onStickyModeCommand(cursors.Range.fromNode(input));
+        this.assertDidNotTurnOffForNode(ul2);
+        this.assertDidNotTurnOffForNode(ul2.firstChild);
+        this.assertDidNotTurnOffForNode(contenteditable);
+
+        // Finally, verify sticky mode isn't impacted on non-editables.
+        this.assertDidNotTurnOffForNode(p);
+        this.ssm_.onStickyModeCommand(cursors.Range.fromNode(p));
+        this.assertDidNotTurnOffForNode(p);
+        this.ssm_.onStickyModeCommand(cursors.Range.fromNode(p));
+        this.assertDidNotTurnOffForNode(p);
       });
-});
+    });
diff --git a/chrome/browser/resources/chromeos/accessibility/common/automation_predicate.js b/chrome/browser/resources/chromeos/accessibility/common/automation_predicate.js
index e6592ca..bda4649 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/automation_predicate.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/automation_predicate.js
@@ -123,8 +123,8 @@
    */
   static touchLeaf(node) {
     return !!(!node.firstChild && node.name) || node.role == Role.BUTTON ||
-        node.role == Role.POP_UP_BUTTON || node.role == Role.SLIDER ||
-        node.role == Role.TEXT_FIELD ||
+        node.role == Role.POP_UP_BUTTON || node.role == Role.PORTAL ||
+        node.role == Role.SLIDER || node.role == Role.TEXT_FIELD ||
         (node.role == Role.MENU_ITEM && !hasActionableDescendant(node));
   }
 
diff --git a/chrome/browser/resources/chromeos/crostini_upgrader/app.js b/chrome/browser/resources/chromeos/crostini_upgrader/app.js
index 29baf69..550bff5 100644
--- a/chrome/browser/resources/chromeos/crostini_upgrader/app.js
+++ b/chrome/browser/resources/chromeos/crostini_upgrader/app.js
@@ -26,6 +26,7 @@
   UPGRADING: 'upgrading',
   OFFER_RESTORE: 'offerRestore',
   RESTORE: 'restore',
+  RESTORE_SUCCEEDED: 'restoreSucceeded',
   ERROR: 'error',
   CANCELING: 'canceling',
   SUCCEEDED: 'succeeded',
@@ -166,7 +167,7 @@
       }),
       callbackRouter.onRestoreSucceeded.addListener(() => {
         assert(this.state_ === State.RESTORE);
-        this.state_ = State.SUCCEEDED;
+        this.state_ = State.RESTORE_SUCCEEDED;
       }),
       callbackRouter.onRestoreFailed.addListener(() => {
         assert(this.state_ === State.RESTORE);
@@ -201,6 +202,7 @@
   onActionButtonClick_() {
     switch (this.state_) {
       case State.SUCCEEDED:
+      case State.RESTORE_SUCCEEDED:
         BrowserProxy.getInstance().handler.launch();
         this.closeDialog_();
         break;
@@ -304,6 +306,7 @@
       case State.PRECHECKS_FAILED:
       case State.SUCCEEDED:
       case State.OFFER_RESTORE:
+      case State.RESTORE_SUCCEEDED:
         return true;
     }
     return false;
@@ -356,6 +359,9 @@
       case State.RESTORE:
         titleId = 'restoreTitle';
         break;
+      case State.RESTORE_SUCCEEDED:
+        titleId = 'restoreSucceededTitle';
+        break;
       case State.CANCELING:
         titleId = 'cancelingTitle';
         break;
@@ -382,6 +388,7 @@
       case State.ERROR:
         return loadTimeData.getString('cancel');
       case State.SUCCEEDED:
+      case State.RESTORE_SUCCEEDED:
         return loadTimeData.getString('done');
       case State.OFFER_RESTORE:
         return loadTimeData.getString('restore');
@@ -397,6 +404,7 @@
   getCancelButtonLabel_(state) {
     switch (state) {
       case State.SUCCEEDED:
+      case State.RESTORE_SUCCEEDED:
         return loadTimeData.getString('close');
       default:
         return loadTimeData.getString('cancel');
@@ -443,6 +451,9 @@
       case State.RESTORE:
         messageId = 'restoreMessage';
         break;
+      case State.RESTORE_SUCCEEDED:
+        messageId = 'restoreSucceededMessage';
+        break;
       case State.SUCCEEDED:
         messageId = 'succeededMessage';
         break;
@@ -470,6 +481,7 @@
   getIllustrationStyle_(state) {
     switch (state) {
       case State.BACKUP_SUCCEEDED:
+      case State.RESTORE_SUCCEEDED:
       case State.PRECHECKS_FAILED:
       case State.ERROR:
         return 'img-square-illustration';
@@ -485,6 +497,7 @@
   getIllustrationURI_(state) {
     switch (state) {
       case State.BACKUP_SUCCEEDED:
+      case State.RESTORE_SUCCEEDED:
         return 'images/success_illustration.svg';
       case State.PRECHECKS_FAILED:
       case State.ERROR:
@@ -493,6 +506,7 @@
     return 'images/linux_illustration.png';
   },
 
+  /** @private */
   updateProgressLine_() {
     if (this.progressLineNumber_ < this.upgradeProgress_) {
       this.lastProgressLine_ =
diff --git a/chrome/browser/resources/inspect/inspect.css b/chrome/browser/resources/inspect/inspect.css
index e13ea7a5..b2bced22b 100644
--- a/chrome/browser/resources/inspect/inspect.css
+++ b/chrome/browser/resources/inspect/inspect.css
@@ -334,8 +334,8 @@
   position: relative;
 }
 
-#port-forwarding-enable {
-  vertical-align: middle;
+.port-forwarding-container {
+  display: flex;
 }
 
 .close-button {
diff --git a/chrome/browser/resources/inspect/inspect.html b/chrome/browser/resources/inspect/inspect.html
index cdd1e51..50b9762 100644
--- a/chrome/browser/resources/inspect/inspect.html
+++ b/chrome/browser/resources/inspect/inspect.html
@@ -98,7 +98,7 @@
       Specify hosts and ports of the target discovery servers.
   </div>
   <div class="config-buttons">
-    <label class="port-forwarding">
+    <label class="port-forwarding-container">
       <input id="port-forwarding-enable" type="checkbox" disabled>Enable port forwarding
     </label>
     <button id="button-done">Done</button>
diff --git a/chrome/browser/resources/print_preview/ui/dpi_settings.html b/chrome/browser/resources/print_preview/ui/dpi_settings.html
index 5590b9d1..b143c20 100644
--- a/chrome/browser/resources/print_preview/ui/dpi_settings.html
+++ b/chrome/browser/resources/print_preview/ui/dpi_settings.html
@@ -6,7 +6,7 @@
 <print-preview-settings-section>
   <span id="dpi-label" slot="title">$i18n{dpiLabel}</span>
   <div slot="controls">
-    <print-preview-settings-select aria-labelledby="dpi-label"
+    <print-preview-settings-select aria-label="$i18n{dpiLabel}"
         capability="[[capabilityWithLabels_]]" setting-name="dpi"
         settings="{{settings}}" disabled="[[disabled]]">
     </print-preview-settings-select>
diff --git a/chrome/browser/resources/print_preview/ui/media_size_settings.html b/chrome/browser/resources/print_preview/ui/media_size_settings.html
index 3a03c19..bcf5dec9 100644
--- a/chrome/browser/resources/print_preview/ui/media_size_settings.html
+++ b/chrome/browser/resources/print_preview/ui/media_size_settings.html
@@ -6,7 +6,7 @@
 <print-preview-settings-section>
   <span id="media-size-label" slot="title">$i18n{mediaSizeLabel}</span>
   <div slot="controls">
-    <print-preview-settings-select aria-labelledby="media-size-label"
+    <print-preview-settings-select aria-label="$i18n{mediaSizeLabel}"
         capability="[[capability]]" setting-name="mediaSize"
         settings="{{settings}}" disabled="[[disabled]]">
     </print-preview-settings-select>
diff --git a/chrome/browser/resources/print_preview/ui/settings_select.html b/chrome/browser/resources/print_preview/ui/settings_select.html
index 1d870ba..07b8fa9f 100644
--- a/chrome/browser/resources/print_preview/ui/settings_select.html
+++ b/chrome/browser/resources/print_preview/ui/settings_select.html
@@ -5,7 +5,7 @@
   }
 </style>
 <select class="md-select" disabled$="[[disabled]]"
-    value="{{selectedValue::change}}">
+    aria-label$="[[ariaLabel]]" value="{{selectedValue::change}}">
   <template is="dom-repeat" items="[[capability.option]]">
     <option selected="[[isSelected_(item, selectedValue)]]"
         value="[[getValue_(item)]]">
diff --git a/chrome/browser/resources/print_preview/ui/settings_select.js b/chrome/browser/resources/print_preview/ui/settings_select.js
index 8325d8e..140e9bc 100644
--- a/chrome/browser/resources/print_preview/ui/settings_select.js
+++ b/chrome/browser/resources/print_preview/ui/settings_select.js
@@ -32,6 +32,8 @@
   behaviors: [SettingsBehavior, SelectBehavior],
 
   properties: {
+    ariaLabel: String,
+
     /** @type {{ option: Array<!SelectOption> }} */
     capability: Object,
 
diff --git a/chrome/browser/signin/e2e_tests/live_sign_in_test.cc b/chrome/browser/signin/e2e_tests/live_sign_in_test.cc
index bbdf253..89c207ee 100644
--- a/chrome/browser/signin/e2e_tests/live_sign_in_test.cc
+++ b/chrome/browser/signin/e2e_tests/live_sign_in_test.cc
@@ -37,6 +37,11 @@
 namespace signin {
 namespace test {
 
+// A wrapper importing the settings module when the chrome://settings serve the
+// Polymer 3 version.
+const char kSettingsScriptWrapperFormat[] =
+    "import('./settings.js').then(settings => {%s});";
+
 enum class PrimarySyncAccountWait { kWaitForAdded, kWaitForCleared, kNotWait };
 
 // Observes various sign-in events and allows to wait for a specific state of
@@ -197,7 +202,9 @@
     auto* settings_tab = browser()->tab_strip_model()->GetActiveWebContents();
     EXPECT_TRUE(content::ExecuteScript(
         settings_tab,
-        "settings.SyncBrowserProxyImpl.getInstance().startSignIn()"));
+        base::StringPrintf(
+            kSettingsScriptWrapperFormat,
+            "settings.SyncBrowserProxyImpl.getInstance().startSignIn();")));
     SignInFromCurrentPage(test_account, previously_signed_in_accounts);
   }
 
@@ -235,7 +242,9 @@
     auto* settings_tab = browser()->tab_strip_model()->GetActiveWebContents();
     EXPECT_TRUE(content::ExecuteScript(
         settings_tab,
-        "settings.SyncBrowserProxyImpl.getInstance().signOut(false)"));
+        base::StringPrintf(
+            kSettingsScriptWrapperFormat,
+            "settings.SyncBrowserProxyImpl.getInstance().signOut(false)")));
     observer.WaitForAccountChanges(0, PrimarySyncAccountWait::kWaitForCleared);
   }
 
@@ -413,11 +422,13 @@
   GURL settings_url("chrome://settings");
   AddTabAtIndex(0, settings_url, ui::PageTransition::PAGE_TRANSITION_TYPED);
   auto* settings_tab = browser()->tab_strip_model()->GetActiveWebContents();
+  std::string start_syncing_script = base::StringPrintf(
+      "settings.SyncBrowserProxyImpl.getInstance()."
+      "startSyncingWithEmail(\"%s\", true);",
+      test_account.user.c_str());
   EXPECT_TRUE(content::ExecuteScript(
-      settings_tab,
-      base::StringPrintf("settings.SyncBrowserProxyImpl.getInstance()."
-                         "startSyncingWithEmail(\"%s\", true)",
-                         test_account.user.c_str())));
+      settings_tab, base::StringPrintf(kSettingsScriptWrapperFormat,
+                                       start_syncing_script.c_str())));
   EXPECT_TRUE(login_ui_test_utils::CancelSyncConfirmationDialog(
       browser(), base::TimeDelta::FromSeconds(3)));
   observer.WaitForAccountChanges(1, PrimarySyncAccountWait::kWaitForCleared);
diff --git a/chrome/browser/ssl/chrome_security_state_client_browsertest.cc b/chrome/browser/ssl/chrome_security_state_client_browsertest.cc
new file mode 100644
index 0000000..21e8327
--- /dev/null
+++ b/chrome/browser/ssl/chrome_security_state_client_browsertest.cc
@@ -0,0 +1,47 @@
+// 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 "base/callback_forward.h"
+#include "base/run_loop.h"
+#include "chrome/browser/ssl/chrome_security_state_model_delegate.h"
+#include "chrome/browser/ssl/security_state_tab_helper.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "components/security_state/content/android/security_state_client.h"
+#include "content/public/browser/web_contents.h"
+
+class ChromeSecurityStateClientTest : public PlatformBrowserTest {
+ public:
+  ChromeSecurityStateClientTest() = default;
+  ~ChromeSecurityStateClientTest() override = default;
+
+ protected:
+  content::WebContents* GetActiveWebContents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(ChromeSecurityStateClientTest,
+                       CorrectSecurityStatModelDelegateCreated) {
+  content::WebContents* contents = GetActiveWebContents();
+  ASSERT_TRUE(contents);
+
+  SecurityStateTabHelper* helper =
+      SecurityStateTabHelper::FromWebContents(contents);
+  ASSERT_TRUE(helper);
+
+  auto* security_state_client = security_state::GetSecurityStateClient();
+  ASSERT_TRUE(security_state_client);
+  {
+    base::RunLoop run_loop;
+    helper->set_get_security_level_callback_for_tests_(run_loop.QuitClosure());
+    EXPECT_EQ(helper->GetSecurityLevel(),
+              security_state_client->MaybeCreateSecurityStateModelDelegate()
+                  ->GetSecurityLevel(contents));
+
+    // The test won't finish until SecurityStateTabHelper::GetSecurityLevel()
+    // is called.
+    run_loop.Run();
+  }
+}
diff --git a/chrome/browser/ssl/security_state_tab_helper.cc b/chrome/browser/ssl/security_state_tab_helper.cc
index 8491113..fd1815d 100644
--- a/chrome/browser/ssl/security_state_tab_helper.cc
+++ b/chrome/browser/ssl/security_state_tab_helper.cc
@@ -128,6 +128,9 @@
 SecurityStateTabHelper::~SecurityStateTabHelper() {}
 
 security_state::SecurityLevel SecurityStateTabHelper::GetSecurityLevel() {
+  if (get_security_level_callback_for_tests_) {
+    std::move(get_security_level_callback_for_tests_).Run();
+  }
   return security_state::GetSecurityLevel(*GetVisibleSecurityState(),
                                           UsedPolicyInstalledCertificate());
 }
diff --git a/chrome/browser/ssl/security_state_tab_helper.h b/chrome/browser/ssl/security_state_tab_helper.h
index 2a23c52a..fa77a371 100644
--- a/chrome/browser/ssl/security_state_tab_helper.h
+++ b/chrome/browser/ssl/security_state_tab_helper.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/callback.h"
 #include "base/optional.h"
 #include "components/security_state/core/security_state.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -38,6 +39,12 @@
       content::NavigationHandle* navigation_handle) override;
   void DidChangeVisibleSecurityState() override;
 
+  // Used by tests to specify a callback to be called when
+  // GetVisibleSecurityState() is called.
+  void set_get_security_level_callback_for_tests_(base::OnceClosure closure) {
+    get_security_level_callback_for_tests_ = std::move(closure);
+  }
+
  private:
   explicit SecurityStateTabHelper(content::WebContents* web_contents);
   friend class content::WebContentsUserData<SecurityStateTabHelper>;
@@ -54,6 +61,8 @@
                            bool /* should_suppress_legacy_tls_warning */>>
       cached_should_suppress_legacy_tls_warning_;
 
+  base::OnceClosure get_security_level_callback_for_tests_;
+
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 
   DISALLOW_COPY_AND_ASSIGN(SecurityStateTabHelper);
diff --git a/chrome/browser/sync/test/integration/bookmarks_helper.cc b/chrome/browser/sync/test/integration/bookmarks_helper.cc
index 14471ce..3b09ddf 100644
--- a/chrome/browser/sync/test/integration/bookmarks_helper.cc
+++ b/chrome/browser/sync/test/integration/bookmarks_helper.cc
@@ -1227,6 +1227,18 @@
   return expected_count_ == actual_count;
 }
 
+BookmarkFaviconLoadedChecker::BookmarkFaviconLoadedChecker(int profile_index,
+                                                           const GURL& page_url)
+    : SingleBookmarkModelStatusChangeChecker(profile_index),
+      bookmark_node_(GetUniqueNodeByURL(profile_index, page_url)) {
+  DCHECK_NE(nullptr, bookmark_node_);
+}
+
+bool BookmarkFaviconLoadedChecker::IsExitConditionSatisfied(std::ostream* os) {
+  *os << "Waiting for the favicon to be loaded for " << bookmark_node_->url();
+  return bookmark_node_->is_favicon_loaded();
+}
+
 ServerBookmarksEqualityChecker::ServerBookmarksEqualityChecker(
     syncer::ProfileSyncService* service,
     fake_server::FakeServer* fake_server,
diff --git a/chrome/browser/sync/test/integration/bookmarks_helper.h b/chrome/browser/sync/test/integration/bookmarks_helper.h
index ee6081f..d02fab5 100644
--- a/chrome/browser/sync/test/integration/bookmarks_helper.h
+++ b/chrome/browser/sync/test/integration/bookmarks_helper.h
@@ -6,7 +6,9 @@
 #define CHROME_BROWSER_SYNC_TEST_INTEGRATION_BOOKMARKS_HELPER_H_
 
 #include <memory>
+#include <set>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/callback_forward.h"
@@ -420,6 +422,22 @@
   const int expected_count_;
 };
 
+// Checker used to wait until the favicon of a bookmark has been loaded. It
+// doesn't itself trigger the load of the favicon.
+class BookmarkFaviconLoadedChecker
+    : public SingleBookmarkModelStatusChangeChecker {
+ public:
+  // There must be exactly one bookmark for |page_url| in the BookmarkModel in
+  // |profile_index|.
+  BookmarkFaviconLoadedChecker(int profile_index, const GURL& page_url);
+
+  // StatusChangeChecker implementation.
+  bool IsExitConditionSatisfied(std::ostream* os) override;
+
+ private:
+  const bookmarks::BookmarkNode* const bookmark_node_;
+};
+
 // Checker used to block until the bookmarks on the server match a given set of
 // expected bookmarks.
 class ServerBookmarksEqualityChecker : public SingleClientStatusChangeChecker {
diff --git a/chrome/browser/sync/test/integration/single_client_bookmarks_sync_test.cc b/chrome/browser/sync/test/integration/single_client_bookmarks_sync_test.cc
index c6d6abaf..24592464 100644
--- a/chrome/browser/sync/test/integration/single_client_bookmarks_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_bookmarks_sync_test.cc
@@ -37,6 +37,7 @@
 using bookmarks::UrlAndTitle;
 using bookmarks_helper::AddFolder;
 using bookmarks_helper::AddURL;
+using bookmarks_helper::BookmarkFaviconLoadedChecker;
 using bookmarks_helper::BookmarksGUIDChecker;
 using bookmarks_helper::BookmarksMatchVerifierChecker;
 using bookmarks_helper::BookmarksTitleChecker;
@@ -51,6 +52,7 @@
 using bookmarks_helper::GetBookmarkBarNode;
 using bookmarks_helper::GetBookmarkModel;
 using bookmarks_helper::GetOtherNode;
+using bookmarks_helper::GetUniqueNodeByURL;
 using bookmarks_helper::ModelMatchesVerifier;
 using bookmarks_helper::Move;
 using bookmarks_helper::Remove;
@@ -1137,6 +1139,61 @@
   EXPECT_EQ(1u, GetBookmarkBarNode(kSingleProfileIndex)->children().size());
 }
 
+IN_PROC_BROWSER_TEST_P(SingleClientBookmarksSyncTest,
+                       PRE_ShouldNotReploadUponFaviconLoad) {
+  const GURL url = GURL("http://www.foo.com");
+  fake_server::EntityBuilderFactory entity_builder_factory;
+  fake_server::BookmarkEntityBuilder bookmark_builder =
+      entity_builder_factory.NewBookmarkEntityBuilder("Foo Title");
+
+  // Create a legacy bookmark on the server (no GUID field populated). The fact
+  // that it's a legacy bookmark means any locally-produced specifics would be
+  // different for this bookmark (new fields like GUID would be populated).
+  std::unique_ptr<syncer::LoopbackServerEntity> bookmark =
+      bookmark_builder.BuildBookmark(url, /*is_legacy=*/true);
+  ASSERT_TRUE(bookmark.get()->GetSpecifics().bookmark().guid().empty());
+  fake_server_->InjectEntity(std::move(bookmark));
+
+  // Start syncing.
+  DisableVerifier();
+  ASSERT_TRUE(SetupSync());
+  ASSERT_TRUE(BookmarksUrlChecker(kSingleProfileIndex, url, 1).Wait());
+}
+
+IN_PROC_BROWSER_TEST_P(SingleClientBookmarksSyncTest,
+                       ShouldNotReploadUponFaviconLoad) {
+  const GURL url = GURL("http://www.foo.com");
+
+  base::HistogramTester histogram_tester;
+  DisableVerifier();
+  ASSERT_TRUE(SetupClients());
+#if defined(OS_CHROMEOS)
+  // signin::SetRefreshTokenForPrimaryAccount() is needed on ChromeOS in order
+  // to get a non-empty refresh token on startup.
+  GetClient(0)->SignInPrimaryAccount();
+#endif  // defined(OS_CHROMEOS)
+  ASSERT_TRUE(GetClient(kSingleProfileIndex)->AwaitEngineInitialization());
+
+  // Make sure the favicon gets loaded.
+  const BookmarkNode* bookmark_node =
+      GetUniqueNodeByURL(kSingleProfileIndex, url);
+  ASSERT_NE(nullptr, bookmark_node);
+  GetBookmarkModel(kSingleProfileIndex)->GetFavicon(bookmark_node);
+  ASSERT_TRUE(BookmarkFaviconLoadedChecker(kSingleProfileIndex, url).Wait());
+
+  // Make sure all local commits make it to the server.
+  ASSERT_TRUE(
+      UpdatedProgressMarkerChecker(GetSyncService(kSingleProfileIndex)).Wait());
+
+  // Verify that the bookmark hasn't been uploaded (no local updates issued). No
+  // commits are expected despite the fact that the server-side bookmark is a
+  // legacy bookmark without the most recent fields (e.g. GUID), because loading
+  // favicons should not lead to commits unless the favicon itself changed.
+  EXPECT_EQ(
+      0, histogram_tester.GetBucketCount("Sync.ModelTypeEntityChange3.BOOKMARK",
+                                         /*LOCAL_UPDATE=*/2));
+}
+
 IN_PROC_BROWSER_TEST_P(
     SingleClientBookmarksSyncTestWithEnabledReuploadRemoteBookmarks,
     ShouldReuploadFullTitleAfterInitialMerge) {
diff --git a/chrome/browser/thumbnail/generator/android/thumbnail_media_parser_impl.cc b/chrome/browser/thumbnail/generator/android/thumbnail_media_parser_impl.cc
index 3c79ee2..dc6ed18af 100644
--- a/chrome/browser/thumbnail/generator/android/thumbnail_media_parser_impl.cc
+++ b/chrome/browser/thumbnail/generator/android/thumbnail_media_parser_impl.cc
@@ -20,7 +20,6 @@
 #include "media/base/video_thumbnail_decoder.h"
 #include "media/mojo/clients/mojo_video_decoder.h"
 #include "media/mojo/mojom/media_service.mojom.h"
-#include "media/mojo/services/media_interface_provider.h"
 #include "media/renderers/paint_canvas_video_renderer.h"
 #include "media/video/gpu_video_accelerator_factories.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -267,9 +266,11 @@
 media::mojom::InterfaceFactory*
 ThumbnailMediaParserImpl::GetMediaInterfaceFactory() {
   if (!media_interface_factory_) {
-    mojo::PendingRemote<service_manager::mojom::InterfaceProvider> interfaces;
-    media_interface_provider_ = std::make_unique<media::MediaInterfaceProvider>(
-        interfaces.InitWithNewPipeAndPassReceiver());
+    // No need to provide any remote services to the media service because the
+    // ThumbnailMediaParser does not use them, but the Mojo argument is
+    // currently marked as required so pass a remote but drop the other end.
+    mojo::PendingRemote<media::mojom::FrameInterfaceFactory> interfaces;
+    ignore_result(interfaces.InitWithNewPipeAndPassReceiver());
     content::GetMediaService().CreateInterfaceFactory(
         media_interface_factory_.BindNewPipeAndPassReceiver(),
         std::move(interfaces));
diff --git a/chrome/browser/thumbnail/generator/android/thumbnail_media_parser_impl.h b/chrome/browser/thumbnail/generator/android/thumbnail_media_parser_impl.h
index 1278081..32eb204 100644
--- a/chrome/browser/thumbnail/generator/android/thumbnail_media_parser_impl.h
+++ b/chrome/browser/thumbnail/generator/android/thumbnail_media_parser_impl.h
@@ -26,7 +26,6 @@
 
 namespace media {
 class GpuVideoAcceleratorFactories;
-class MediaInterfaceProvider;
 class MojoVideoDecoder;
 class VideoDecoderConfig;
 class VideoThumbnailDecoder;
@@ -115,7 +114,6 @@
   media::VideoDecoderConfig config_;
   std::unique_ptr<media::VideoThumbnailDecoder> decoder_;
   mojo::Remote<media::mojom::InterfaceFactory> media_interface_factory_;
-  std::unique_ptr<media::MediaInterfaceProvider> media_interface_provider_;
   std::unique_ptr<media::GpuVideoAcceleratorFactories> gpu_factories_;
   bool decode_done_;
 
diff --git a/chrome/browser/ui/app_list/app_context_menu.cc b/chrome/browser/ui/app_list/app_context_menu.cc
index 866c74e..4eeaeba 100644
--- a/chrome/browser/ui/app_list/app_context_menu.cc
+++ b/chrome/browser/ui/app_list/app_context_menu.cc
@@ -10,6 +10,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/vector_icon_types.h"
 #include "ui/views/vector_icons.h"
@@ -156,7 +157,8 @@
 
   const gfx::VectorIcon& icon = GetMenuItemVectorIcon(command_id, string_id);
   if (!icon.is_empty()) {
-    menu_model->AddItemWithStringIdAndIcon(command_id, string_id, icon);
+    menu_model->AddItemWithStringIdAndIcon(
+        command_id, string_id, ui::ImageModel::FromVectorIcon(icon));
     return;
   }
   // Check items use default icons.
diff --git a/chrome/browser/ui/app_list/extension_app_context_menu.cc b/chrome/browser/ui/app_list/extension_app_context_menu.cc
index 4fb1f7ea..89c931f8 100644
--- a/chrome/browser/ui/app_list/extension_app_context_menu.cc
+++ b/chrome/browser/ui/app_list/extension_app_context_menu.cc
@@ -19,6 +19,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/context_menu_params.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/controls/menu/menu_config.h"
 #include "ui/views/vector_icons.h"
@@ -224,7 +225,8 @@
       kGroupId);
   menu_model->AddActionableSubmenuWithStringIdAndIcon(
       ash::LAUNCH_NEW, GetLaunchStringId(), open_new_submenu_model_.get(),
-      GetMenuItemVectorIcon(ash::LAUNCH_NEW, GetLaunchStringId()));
+      ui::ImageModel::FromVectorIcon(
+          GetMenuItemVectorIcon(ash::LAUNCH_NEW, GetLaunchStringId())));
 }
 
 }  // namespace app_list
diff --git a/chrome/browser/ui/app_list/extension_app_utils.cc b/chrome/browser/ui/app_list/extension_app_utils.cc
index efd50666..7a450d5 100644
--- a/chrome/browser/ui/app_list/extension_app_utils.cc
+++ b/chrome/browser/ui/app_list/extension_app_utils.cc
@@ -12,6 +12,7 @@
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/models/simple_menu_model.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/controls/menu/menu_config.h"
@@ -53,7 +54,8 @@
     const int index = start_index + i;
     if (menu_model->GetLabelAt(index) ==
         l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW)) {
-      menu_model->SetIcon(index, views::kNewWindowIcon);
+      menu_model->SetIcon(
+          index, ui::ImageModel::FromVectorIcon(views::kNewWindowIcon));
     }
   }
 }
diff --git a/chrome/browser/ui/app_list/search/app_search_provider.cc b/chrome/browser/ui/app_list/search/app_search_provider.cc
index 33e5539..45d6e85 100644
--- a/chrome/browser/ui/app_list/search/app_search_provider.cc
+++ b/chrome/browser/ui/app_list/search/app_search_provider.cc
@@ -60,9 +60,9 @@
 
 // Parameters for FuzzyTokenizedStringMatch.
 constexpr bool kUsePrefixOnly = false;
-constexpr bool kUseWeightedRatio = true;
+constexpr bool kUseWeightedRatio = false;
 constexpr bool kUseEditDistance = false;
-constexpr double kRelevanceThreshold = 0.3;
+constexpr double kRelevanceThreshold = 0.32;
 constexpr double kPartialMatchPenaltyRate = 0.9;
 
 // Adds |app_result| to |results| only in case no duplicate apps were already
diff --git a/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc b/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc
index f40b160..7f7a9d8 100644
--- a/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc
+++ b/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc
@@ -797,7 +797,7 @@
   feature_list.InitAndEnableFeature(app_list_features::kEnableFuzzyAppSearch);
   CreateSearch();
   EXPECT_EQ("Packaged App 1,Packaged App 2", RunQuery("pa"));
-  std::string result = RunQuery("packahe");
+  std::string result = RunQuery("ackaged");
   EXPECT_TRUE(result == "Packaged App 1,Packaged App 2" ||
               result == "Packaged App 2,Packaged App 1");
   EXPECT_EQ(kKeyboardShortcutHelperInternalName, RunQuery("Helper"));
diff --git a/chrome/browser/ui/app_list/web_app_context_menu.cc b/chrome/browser/ui/app_list/web_app_context_menu.cc
index 4232b578..f6e8c23 100644
--- a/chrome/browser/ui/app_list/web_app_context_menu.cc
+++ b/chrome/browser/ui/app_list/web_app_context_menu.cc
@@ -17,6 +17,7 @@
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/controls/menu/menu_config.h"
 #include "ui/views/vector_icons.h"
@@ -155,7 +156,8 @@
       kGroupId);
   menu_model->AddActionableSubmenuWithStringIdAndIcon(
       ash::LAUNCH_NEW, GetLaunchStringId(), open_new_submenu_model_.get(),
-      GetMenuItemVectorIcon(ash::LAUNCH_NEW, GetLaunchStringId()));
+      ui::ImageModel::FromVectorIcon(
+          GetMenuItemVectorIcon(ash::LAUNCH_NEW, GetLaunchStringId())));
 }
 
 web_app::WebAppProvider& WebAppContextMenu::GetProvider() const {
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc
index f0a57fc..74422ea 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc
@@ -51,6 +51,7 @@
 #include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
+#include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
 #include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_test_util.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_util.h"
@@ -71,6 +72,7 @@
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
 #include "chrome/browser/web_applications/components/app_registry_controller.h"
 #include "chrome/browser/web_applications/components/app_shortcut_manager.h"
+#include "chrome/browser/web_applications/components/externally_installed_web_app_prefs.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
 #include "chrome/browser/web_applications/components/web_app_id.h"
@@ -82,6 +84,7 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
+#include "chrome/common/pref_names.h"
 #include "chrome/common/web_application_info.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -2276,6 +2279,52 @@
       ChromeLauncherController::instance()->shelf_model()->active_shelf_id());
 }
 
+IN_PROC_BROWSER_TEST_P(ShelfWebAppBrowserTest, WebAppPolicy) {
+  // Install web app.
+  GURL app_url = GURL("https://example.org/");
+  web_app::AppId app_id = InstallWebApp(app_url);
+  web_app::ExternallyInstalledWebAppPrefs web_app_prefs(
+      browser()->profile()->GetPrefs());
+  web_app_prefs.Insert(app_url, app_id,
+                       web_app::ExternalInstallSource::kExternalPolicy);
+  apps::AppServiceProxyFactory::GetForProfile(profile())
+      ->FlushMojoCallsForTesting();
+
+  // Set policy to pin the web app.
+  base::DictionaryValue entry;
+  entry.SetKey(kPinnedAppsPrefAppIDKey, base::Value(app_url.spec()));
+  base::ListValue policy_value;
+  policy_value.Append(std::move(entry));
+  profile()->GetPrefs()->Set(prefs::kPolicyPinnedLauncherApps, policy_value);
+
+  // Check web app is pinned and fixed.
+  EXPECT_EQ(shelf_model()->item_count(), 2);
+  EXPECT_EQ(shelf_model()->items()[0].type, ash::TYPE_BROWSER_SHORTCUT);
+  EXPECT_EQ(shelf_model()->items()[1].type, ash::TYPE_PINNED_APP);
+  EXPECT_EQ(shelf_model()->items()[1].id.app_id, app_id);
+  EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
+            GetPinnableForAppID(app_id, profile()));
+}
+
+IN_PROC_BROWSER_TEST_P(ShelfWebAppBrowserTest, WebAppPolicyNonExistentApp) {
+  // Don't install the web app.
+  GURL app_url = GURL("https://example.org/");
+  web_app::AppId app_id = web_app::GenerateAppIdFromURL(app_url);
+
+  // Set policy to pin the non existent web app.
+  base::DictionaryValue entry;
+  entry.SetKey(kPinnedAppsPrefAppIDKey, base::Value(app_url.spec()));
+  base::ListValue policy_value;
+  policy_value.Append(std::move(entry));
+  profile()->GetPrefs()->Set(prefs::kPolicyPinnedLauncherApps, policy_value);
+
+  // Check web app policy is ignored.
+  EXPECT_EQ(shelf_model()->item_count(), 1);
+  EXPECT_EQ(shelf_model()->items()[0].type, ash::TYPE_BROWSER_SHORTCUT);
+  EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
+            GetPinnableForAppID(app_id, profile()));
+}
+
 // Test that "Close" is shown in the context menu when there are opened browsers
 // windows.
 IN_PROC_BROWSER_TEST_F(ShelfAppBrowserTest,
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
index 3c65c542..dc33d2a 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
@@ -151,6 +151,7 @@
 constexpr char kOfflineGmailUrl[] = "https://mail.google.com/mail/mu/u";
 constexpr char kGmailUrl[] = "https://mail.google.com/mail/u";
 constexpr char kGmailLaunchURL[] = "https://mail.google.com/mail/ca";
+constexpr char kLaunchURL[] = "https://foo.example/";
 
 // An extension prefix.
 constexpr char kCrxAppPrefix[] = "_crx_";
@@ -159,10 +160,6 @@
 // pin model with default apps that can affect some tests.
 constexpr char kDummyAppId[] = "dummyappid_dummyappid_dummyappid";
 
-// Web App id.
-constexpr char kWebAppId[] = "lpikggcgamknpihimepdkohalcnpofed";
-constexpr char kWebAppUrl[] = "https://foo.example/";
-
 // Test implementation of AppIconLoader.
 class TestAppIconLoaderImpl : public AppIconLoader {
  public:
@@ -299,7 +296,7 @@
     // AppService checks the app's type. So set the
     // manifest_keys::kLaunchWebURL, so that the extension can get the type
     // from manifest value, and then AppService can get the extension's type.
-    manifest.SetString(extensions::manifest_keys::kLaunchWebURL, kWebAppUrl);
+    manifest.SetString(extensions::manifest_keys::kLaunchWebURL, kLaunchURL);
 
     base::DictionaryValue manifest_platform_app;
     manifest_platform_app.SetString(extensions::manifest_keys::kName,
@@ -408,10 +405,7 @@
     // manifest_keys::kLaunchWebURL, so that the extension can get the type
     // from manifest value, and then AppService can get the extension's type.
     manifest_web_app.SetString(extensions::manifest_keys::kLaunchWebURL,
-                               kWebAppUrl);
-    web_app_ = Extension::Create(base::FilePath(), Manifest::UNPACKED,
-                                 manifest_web_app, Extension::FROM_BOOKMARK,
-                                 kWebAppId, &error);
+                               kLaunchURL);
   }
 
   ui::BaseWindow* GetLastActiveWindowForItemController(
@@ -775,8 +769,6 @@
             result += "Play Store";
           } else if (app == crostini::GetTerminalId()) {
             result += "Terminal";
-          } else if (app == web_app_->id()) {
-            result += "WebApp";
           } else {
             bool arc_app_found = false;
             for (const auto& arc_app : arc_test_.fake_apps()) {
@@ -946,7 +938,6 @@
   scoped_refptr<Extension> extensionYoutubeApp_;
   scoped_refptr<Extension> extension_platform_app_;
   scoped_refptr<Extension> arc_support_host_;
-  scoped_refptr<Extension> web_app_;
 
   ArcAppTest arc_test_;
   bool auto_start_arc_test_ = false;
@@ -1024,7 +1015,7 @@
     // AppService checks the app's type. So set the
     // manifest_keys::kLaunchWebURL, so that the extension can get the type
     // from manifest value, and then AppService can get the extension's type.
-    manifest.SetString(extensions::manifest_keys::kLaunchWebURL, kWebAppUrl);
+    manifest.SetString(extensions::manifest_keys::kLaunchWebURL, kLaunchURL);
 
     const std::vector<std::pair<std::string, std::string>> extra_extensions = {
         {extension_misc::kCalendarAppId, "Calendar"},
@@ -2998,42 +2989,6 @@
   EXPECT_EQ("Chrome, App1, App2", GetPinnedAppStatus());
 }
 
-TEST_F(ChromeLauncherControllerTest, WebAppPolicy) {
-  // Simulate one Web App being installed.
-  web_app::ExternallyInstalledWebAppPrefs web_app_prefs(profile()->GetPrefs());
-  web_app_prefs.Insert(GURL(kWebAppUrl), kWebAppId,
-                       web_app::ExternalInstallSource::kExternalPolicy);
-  extension_service_->AddExtension(web_app_.get());
-
-  // Set the policy value.
-  base::ListValue policy_value;
-  AppendPrefValue(&policy_value, kWebAppUrl);
-  profile()->GetTestingPrefService()->SetManagedPref(
-      prefs::kPolicyPinnedLauncherApps,
-      base::Value::ToUniquePtrValue(std::move(policy_value)));
-
-  InitLauncherController();
-
-  EXPECT_EQ("Chrome, WebApp", GetPinnedAppStatus());
-  EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
-            GetPinnableForAppID(kWebAppId, profile()));
-}
-
-TEST_F(ChromeLauncherControllerTest, WebAppPolicyNonExistentApp) {
-  // Set the policy value but don't install an app for it.
-  base::ListValue policy_value;
-  AppendPrefValue(&policy_value, kWebAppUrl);
-  profile()->GetTestingPrefService()->SetManagedPref(
-      prefs::kPolicyPinnedLauncherApps,
-      base::Value::ToUniquePtrValue(std::move(policy_value)));
-
-  InitLauncherController();
-
-  EXPECT_EQ("Chrome", GetPinnedAppStatus());
-  EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
-            GetPinnableForAppID(kWebAppId, profile()));
-}
-
 TEST_F(ChromeLauncherControllerTest, UnpinWithUninstall) {
   extension_service_->AddExtension(extensionGmailApp_.get());
   extension_service_->AddExtension(extensionDocApp_.get());
diff --git a/chrome/browser/ui/ash/launcher/extension_shelf_context_menu.cc b/chrome/browser/ui/ash/launcher/extension_shelf_context_menu.cc
index 8a75f0f..96758fd 100644
--- a/chrome/browser/ui/ash/launcher/extension_shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/launcher/extension_shelf_context_menu.cc
@@ -25,6 +25,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/context_menu_params.h"
 #include "extensions/browser/extension_prefs.h"
+#include "ui/base/models/image_model.h"
 #include "ui/display/screen.h"
 #include "ui/gfx/paint_vector_icon.h"
 
@@ -227,7 +228,8 @@
   menu_model->AddActionableSubmenuWithStringIdAndIcon(
       ash::MENU_OPEN_NEW, GetLaunchTypeStringId(),
       open_new_submenu_model_.get(),
-      GetCommandIdVectorIcon(ash::MENU_OPEN_NEW, GetLaunchTypeStringId()));
+      ui::ImageModel::FromVectorIcon(
+          GetCommandIdVectorIcon(ash::MENU_OPEN_NEW, GetLaunchTypeStringId())));
 }
 
 extensions::LaunchType ExtensionShelfContextMenu::GetLaunchType() const {
diff --git a/chrome/browser/ui/ash/launcher/shelf_context_menu.cc b/chrome/browser/ui/ash/launcher/shelf_context_menu.cc
index c6ed3bb..5b5602b 100644
--- a/chrome/browser/ui/ash/launcher/shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/launcher/shelf_context_menu.cc
@@ -29,6 +29,7 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/vector_icons/vector_icons.h"
+#include "ui/base/models/image_model.h"
 #include "ui/display/types/display_constants.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/vector_icons.h"
@@ -306,7 +307,8 @@
 
   const gfx::VectorIcon& icon = GetCommandIdVectorIcon(type, string_id);
   if (!icon.is_empty()) {
-    menu_model->AddItemWithStringIdAndIcon(type, string_id, icon);
+    menu_model->AddItemWithStringIdAndIcon(
+        type, string_id, ui::ImageModel::FromVectorIcon(icon));
     return;
   }
   // If the MenuType is a check item.
diff --git a/chrome/browser/ui/ash/launcher/shelf_context_menu_unittest.cc b/chrome/browser/ui/ash/launcher/shelf_context_menu_unittest.cc
index 85b9d231..b4163a3 100644
--- a/chrome/browser/ui/ash/launcher/shelf_context_menu_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/shelf_context_menu_unittest.cc
@@ -570,11 +570,8 @@
       GetContextMenu(item_delegate, primary_id);
 
   // Check that every menu item has an icon
-  for (int i = 0; i < menu->GetItemCount(); ++i) {
-    const gfx::VectorIcon* icon = menu->GetVectorIconAt(i);
-    ASSERT_TRUE(icon);
-    EXPECT_FALSE(icon->is_empty());
-  }
+  for (int i = 0; i < menu->GetItemCount(); ++i)
+    EXPECT_FALSE(menu->GetIconAt(i).IsEmpty());
 
   // When crostini is running, the terminal should have an option to kill the
   // vm.
@@ -612,11 +609,8 @@
       GetContextMenu(item_delegate, primary_id);
 
   // Check that every menu item has an icon
-  for (int i = 0; i < menu->GetItemCount(); ++i) {
-    const gfx::VectorIcon* icon = menu->GetVectorIconAt(i);
-    ASSERT_TRUE(icon);
-    EXPECT_FALSE(icon->is_empty());
-  }
+  for (int i = 0; i < menu->GetItemCount(); ++i)
+    EXPECT_FALSE(menu->GetIconAt(i).IsEmpty());
 
   // Precisely which density option is shown is not important to us, we only
   // care that one is shown.
diff --git a/chrome/browser/ui/global_error/global_error.cc b/chrome/browser/ui/global_error/global_error.cc
index 0a384e9..cd53a78 100644
--- a/chrome/browser/ui/global_error/global_error.cc
+++ b/chrome/browser/ui/global_error/global_error.cc
@@ -10,7 +10,6 @@
 #include "chrome/grit/theme_resources.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/ui_base_types.h"
-#include "ui/gfx/image/image.h"
 
 #if !defined(OS_ANDROID)
 #include "chrome/app/vector_icons/vector_icons.h"
@@ -26,22 +25,22 @@
 
 GlobalError::Severity GlobalError::GetSeverity() { return SEVERITY_MEDIUM; }
 
-gfx::Image GlobalError::MenuItemIcon() {
+ui::ImageModel GlobalError::MenuItemIcon() {
 #if defined(OS_ANDROID)
-  return ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
-      IDR_INPUT_ALERT_MENU);
+  return ui::ImageModel(
+      ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
+          IDR_INPUT_ALERT_MENU));
 #else
-  return gfx::Image(
-      gfx::CreateVectorIcon(kBrowserToolsErrorIcon, gfx::kGoogleYellow700));
+  return ui::ImageModel::FromVectorIcon(kBrowserToolsErrorIcon,
+                                        gfx::kGoogleYellow700);
 #endif
 }
 
 // GlobalErrorWithStandardBubble ---------------------------------------------
 
-GlobalErrorWithStandardBubble::GlobalErrorWithStandardBubble()
-    : has_shown_bubble_view_(false), bubble_view_(NULL) {}
+GlobalErrorWithStandardBubble::GlobalErrorWithStandardBubble() = default;
 
-GlobalErrorWithStandardBubble::~GlobalErrorWithStandardBubble() {}
+GlobalErrorWithStandardBubble::~GlobalErrorWithStandardBubble() = default;
 
 bool GlobalErrorWithStandardBubble::HasBubbleView() { return true; }
 
diff --git a/chrome/browser/ui/global_error/global_error.h b/chrome/browser/ui/global_error/global_error.h
index 9c447fa..6c8a6db0 100644
--- a/chrome/browser/ui/global_error/global_error.h
+++ b/chrome/browser/ui/global_error/global_error.h
@@ -10,14 +10,11 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string16.h"
+#include "ui/base/models/image_model.h"
 
 class Browser;
 class GlobalErrorBubbleViewBase;
 
-namespace gfx {
-class Image;
-}
-
 // This object describes a single global error.
 class GlobalError {
  public:
@@ -42,7 +39,7 @@
   // Returns the label for the menu item.
   virtual base::string16 MenuItemLabel() = 0;
   // Returns the menu item icon.
-  virtual gfx::Image MenuItemIcon();
+  virtual ui::ImageModel MenuItemIcon();
   // Called when the user clicks on the menu item.
   virtual void ExecuteMenuItem(Browser* browser) = 0;
 
@@ -95,8 +92,8 @@
   virtual void BubbleViewDidClose(Browser* browser);
 
  private:
-  bool has_shown_bubble_view_;
-  GlobalErrorBubbleViewBase* bubble_view_;
+  bool has_shown_bubble_view_ = false;
+  GlobalErrorBubbleViewBase* bubble_view_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(GlobalErrorWithStandardBubble);
 };
diff --git a/chrome/browser/ui/messages/android/BUILD.gn b/chrome/browser/ui/messages/android/BUILD.gn
index 05e17f7..82ba8733 100644
--- a/chrome/browser/ui/messages/android/BUILD.gn
+++ b/chrome/browser/ui/messages/android/BUILD.gn
@@ -56,7 +56,7 @@
     "//components/browser_ui/widget/android:java",
     "//components/infobars/core:infobar_enums_java",
     "//third_party/android_deps:androidx_annotation_annotation_java",
-    "//third_party/android_deps:com_android_support_appcompat_v7_java",
+    "//third_party/android_deps:androidx_appcompat_appcompat_java",
     "//ui/android:ui_full_java",
     "//ui/android:ui_utils_java",
   ]
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index f524bbb..5e2f711 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -964,8 +964,17 @@
     return;
   }
   StartupBrowserCreator startup_browser_creator;
-  startup_browser_creator.ProcessCmdLineImpl(
-      command_line, cur_dir, /*process_startup=*/false, profile, Profiles());
+  Profiles last_opened_profiles;
+#if !defined(OS_CHROMEOS)
+  // On ChromeOS multiple profiles doesn't apply.
+  // If no browser windows are open, i.e. the browser is being kept alive in
+  // background mode or for other processing, restore |last_opened_profiles|.
+  if (chrome::GetTotalBrowserCount() == 0)
+    last_opened_profiles = profile_manager->GetLastOpenedProfiles();
+#endif  // defined(OS_CHROMEOS)
+  startup_browser_creator.ProcessCmdLineImpl(command_line, cur_dir,
+                                             /*process_startup=*/false, profile,
+                                             last_opened_profiles);
 }
 
 // static
diff --git a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
index 95514bbc..991e6a03 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
@@ -19,6 +19,7 @@
 #include "base/threading/thread_restrictions.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
+#include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
@@ -125,7 +126,6 @@
 #endif
 }
 
-
 void DisableWelcomePages(const std::vector<Profile*>& profiles) {
   for (Profile* profile : profiles)
     profile->GetPrefs()->SetBoolean(prefs::kHasSeenWelcomePage, true);
@@ -149,6 +149,73 @@
 
 typedef base::Optional<policy::PolicyLevel> PolicyVariant;
 
+// This class waits until all browser windows are closed, and then runs
+// a quit closure.
+class AllBrowsersClosedWaiter : public BrowserListObserver {
+ public:
+  explicit AllBrowsersClosedWaiter(base::OnceClosure quit_closure);
+  AllBrowsersClosedWaiter(const AllBrowsersClosedWaiter&) = delete;
+  AllBrowsersClosedWaiter& operator=(const AllBrowsersClosedWaiter&) = delete;
+  ~AllBrowsersClosedWaiter() override;
+
+  // BrowserListObserver:
+  void OnBrowserRemoved(Browser* browser) override;
+
+ private:
+  base::OnceClosure quit_closure_;
+};
+
+AllBrowsersClosedWaiter::AllBrowsersClosedWaiter(base::OnceClosure quit_closure)
+    : quit_closure_(std::move(quit_closure)) {
+  BrowserList::AddObserver(this);
+}
+
+AllBrowsersClosedWaiter::~AllBrowsersClosedWaiter() {
+  BrowserList::RemoveObserver(this);
+}
+
+void AllBrowsersClosedWaiter::OnBrowserRemoved(Browser* browser) {
+  if (chrome::GetTotalBrowserCount() == 0)
+    std::move(quit_closure_).Run();
+}
+
+// This class waits for a specified number of sessions to be restored.
+class SessionsRestoredWaiter {
+ public:
+  explicit SessionsRestoredWaiter(base::OnceClosure quit_closure,
+                                  int num_session_restores_expected);
+  SessionsRestoredWaiter(const SessionsRestoredWaiter&) = delete;
+  SessionsRestoredWaiter& operator=(const SessionsRestoredWaiter&) = delete;
+  ~SessionsRestoredWaiter();
+
+ private:
+  // Callback for session restore notifications.
+  void OnSessionRestoreDone(int num_tabs_restored);
+
+  // For automatically unsubscribing from callback-based notifications.
+  SessionRestore::CallbackSubscription callback_subscription_;
+  base::OnceClosure quit_closure_;
+  int num_session_restores_expected_;
+  int num_sessions_restored_ = 0;
+};
+
+SessionsRestoredWaiter::SessionsRestoredWaiter(
+    base::OnceClosure quit_closure,
+    int num_session_restores_expected)
+    : quit_closure_(std::move(quit_closure)),
+      num_session_restores_expected_(num_session_restores_expected) {
+  callback_subscription_ = SessionRestore::RegisterOnSessionRestoredCallback(
+      base::BindRepeating(&SessionsRestoredWaiter::OnSessionRestoreDone,
+                          base::Unretained(this)));
+}
+
+SessionsRestoredWaiter::~SessionsRestoredWaiter() = default;
+
+void SessionsRestoredWaiter::OnSessionRestoreDone(int num_tabs_restored) {
+  if (++num_sessions_restored_ == num_session_restores_expected_)
+    std::move(quit_closure_).Run();
+}
+
 }  // namespace
 
 class StartupBrowserCreatorTest : public extensions::ExtensionBrowserTest {
@@ -859,6 +926,85 @@
   ASSERT_EQ(0u, chrome::GetBrowserCount(profile_home2));
 }
 
+// This tests that opening multiple profiles with session restore enabled,
+// shutting down, and then launching with kNoStartupWindow doesn't restore
+// the previously opened profiles.
+IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorTest, RestoreWithNoStartupWindow) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+
+  // Create 2 more profiles.
+  base::FilePath dest_path1 = profile_manager->user_data_dir().Append(
+      FILE_PATH_LITERAL("New Profile 1"));
+  base::FilePath dest_path2 = profile_manager->user_data_dir().Append(
+      FILE_PATH_LITERAL("New Profile 2"));
+
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  Profile* profile1 = profile_manager->GetProfile(dest_path1);
+  ASSERT_TRUE(profile1);
+  Profile* profile2 = profile_manager->GetProfile(dest_path2);
+  ASSERT_TRUE(profile2);
+
+  DisableWelcomePages({profile1, profile2});
+
+  // Set the profiles to open last visited pages.
+  SessionStartupPref pref_last(SessionStartupPref::LAST);
+  SessionStartupPref::SetStartupPref(profile1, pref_last);
+  SessionStartupPref::SetStartupPref(profile2, pref_last);
+
+  auto keep_alive = std::make_unique<ScopedKeepAlive>(
+      KeepAliveOrigin::SESSION_RESTORE, KeepAliveRestartOption::DISABLED);
+
+  Profile* default_profile = browser()->profile();
+
+  // Open a page with profile1 and profile2.
+  Browser* browser1 = Browser::Create({Browser::TYPE_NORMAL, profile1, true});
+  chrome::NewTab(browser1);
+  ui_test_utils::NavigateToURL(browser1,
+                               embedded_test_server()->GetURL("/empty.html"));
+
+  Browser* browser2 = Browser::Create({Browser::TYPE_NORMAL, profile2, true});
+  chrome::NewTab(browser2);
+  ui_test_utils::NavigateToURL(browser2,
+                               embedded_test_server()->GetURL("/empty.html"));
+  // Close the browsers.
+  chrome::ExecuteCommand(browser(), IDC_EXIT);
+  {
+    base::RunLoop run_loop;
+    AllBrowsersClosedWaiter waiter(run_loop.QuitClosure());
+    run_loop.Run();
+  }
+  base::CommandLine dummy(base::CommandLine::NO_PROGRAM);
+  dummy.AppendSwitch(switches::kNoStartupWindow);
+
+  StartupBrowserCreator browser_creator;
+  std::vector<Profile*> last_opened_profiles = {profile1, profile2};
+  browser_creator.Start(dummy, profile_manager->user_data_dir(),
+                        default_profile, last_opened_profiles);
+
+  // TODO(davidbienvenu): Waiting for some sort of browser is started
+  // notification would be better. But, we're not opening any browser
+  // windows, so we'd need to invent a new notification.
+  content::RunAllTasksUntilIdle();
+
+  // No browser windows should be opened.
+  EXPECT_EQ(chrome::GetBrowserCount(profile1), 0u);
+  EXPECT_EQ(chrome::GetBrowserCount(profile2), 0u);
+
+  base::CommandLine empty(base::CommandLine::NO_PROGRAM);
+  base::RunLoop run_loop;
+  SessionsRestoredWaiter restore_waiter(run_loop.QuitClosure(), 2);
+
+  StartupBrowserCreator::ProcessCommandLineAlreadyRunning(empty, {},
+                                                          dest_path1);
+  run_loop.Run();
+
+  // profile1 and profile2 browser windows should be opened.
+  EXPECT_EQ(chrome::GetBrowserCount(profile1), 1u);
+  EXPECT_EQ(chrome::GetBrowserCount(profile2), 1u);
+}
+
 // Flaky. See https://crbug.com/819976.
 IN_PROC_BROWSER_TEST_F(StartupBrowserCreatorTest,
                        DISABLED_ProfilesLaunchedAfterCrash) {
diff --git a/chrome/browser/ui/startup/startup_browser_creator_impl.cc b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
index 03d2b4e..e59b1f6 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_impl.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
@@ -619,7 +619,7 @@
     bool process_startup,
     const std::vector<GURL>& cmd_line_urls) {
   // Don't open any browser windows if starting up in "background mode".
-  if (process_startup && command_line_.HasSwitch(switches::kNoStartupWindow))
+  if (command_line_.HasSwitch(switches::kNoStartupWindow))
     return;
 
   StartupTabs cmd_line_tabs;
diff --git a/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.cc b/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.cc
index 1e51d01..83def75 100644
--- a/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.cc
+++ b/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.cc
@@ -17,6 +17,7 @@
 #include "components/tab_groups/tab_group_color.h"
 #include "components/tab_groups/tab_group_id.h"
 #include "components/tab_groups/tab_group_visual_data.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/image/canvas_image_source.h"
 #include "ui/gfx/image/image_skia.h"
@@ -53,9 +54,11 @@
       constexpr int kIconSize = 14;
       const int color_id =
           GetTabGroupContextMenuColorId(tab_group->visual_data()->color());
+      // TODO (kylixrd): Investigate passing in color_id in order to color the
+      // icon using the ColorProvider.
       AddItemWithIcon(group_index, displayed_title,
-                      gfx::CreateVectorIcon(kTabGroupIcon, kIconSize,
-                                            tp.GetColor(color_id)));
+                      ui::ImageModel::FromVectorIcon(
+                          kTabGroupIcon, tp.GetColor(color_id), kIconSize));
     }
     group_index++;
   }
diff --git a/chrome/browser/ui/tabs/tab_menu_model.cc b/chrome/browser/ui/tabs/tab_menu_model.cc
index 24a4fbd..efed516b 100644
--- a/chrome/browser/ui/tabs/tab_menu_model.cc
+++ b/chrome/browser/ui/tabs/tab_menu_model.cc
@@ -119,7 +119,7 @@
                           IDS_CONTEXT_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
                           (send_tab_to_self::GetSingleTargetDeviceName(
                               tab_strip->profile()))),
-                      kSendTabToSelfIcon);
+                      ui::ImageModel::FromVectorIcon(kSendTabToSelfIcon));
 #endif
       send_tab_to_self::RecordSendTabToSelfClickResult(
           send_tab_to_self::kTabMenu,
@@ -136,10 +136,11 @@
                              IDS_CONTEXT_MENU_SEND_TAB_TO_SELF,
                              send_tab_to_self_sub_menu_model_.get());
 #else
-      AddSubMenuWithStringIdAndIcon(TabStripModel::CommandSendTabToSelf,
-                                    IDS_CONTEXT_MENU_SEND_TAB_TO_SELF,
-                                    send_tab_to_self_sub_menu_model_.get(),
-                                    kSendTabToSelfIcon);
+      AddSubMenuWithStringIdAndIcon(
+          TabStripModel::CommandSendTabToSelf,
+          IDS_CONTEXT_MENU_SEND_TAB_TO_SELF,
+          send_tab_to_self_sub_menu_model_.get(),
+          ui::ImageModel::FromVectorIcon(kSendTabToSelfIcon));
 #endif
     }
   }
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index dec6a892..03e1ae46 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -72,6 +72,7 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/layout.h"
 #include "ui/base/models/button_menu_item_model.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/image/image.h"
@@ -86,8 +87,10 @@
 
 #if defined(OS_CHROMEOS)
 #include "ash/public/cpp/tablet_mode.h"
+#include "chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/constants/chromeos_switches.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #endif
 
 #if defined(OS_WIN)
@@ -191,7 +194,7 @@
     if (browser_defaults::kShowHelpMenuItemIcon) {
       ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
       SetIcon(GetIndexOfCommandId(IDC_HELP_PAGE_VIA_MENU),
-              rb.GetNativeImageNamed(IDR_HELP_MENU));
+              ui::ImageModel::FromImage(rb.GetNativeImageNamed(IDR_HELP_MENU)));
     }
     if (browser->profile()->GetPrefs()->GetBoolean(prefs::kUserFeedbackAllowed))
       AddItemWithStringId(IDC_FEEDBACK, IDS_FEEDBACK);
@@ -272,6 +275,18 @@
   tab_strip_model->AddObserver(this);
   Observe(tab_strip_model->GetActiveWebContents());
   UpdateZoomControls();
+
+#if defined(OS_CHROMEOS)
+  PrefService* const local_state = g_browser_process->local_state();
+  if (local_state) {
+    local_state_pref_change_registrar_.Init(local_state);
+    local_state_pref_change_registrar_.Add(
+        policy::policy_prefs::kSystemFeaturesDisableList,
+        base::BindRepeating(&AppMenuModel::UpdateSettingsItemState,
+                            base::Unretained(this)));
+    UpdateSettingsItemState();
+  }
+#endif  // defined(OS_CHROMEOS)
 }
 
 bool AppMenuModel::DoesCommandIdDismissMenu(int command_id) const {
@@ -862,13 +877,13 @@
   if (chrome::ShouldDisplayManagedUi(browser_->profile())) {
     AddSeparator(ui::LOWER_SEPARATOR);
     const int kIconSize = 18;
-    SkColor color = ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
-        ui::NativeTheme::kColorId_HighlightedMenuItemForegroundColor);
-    const auto icon =
-        gfx::CreateVectorIcon(vector_icons::kBusinessIcon, kIconSize, color);
     AddHighlightedItemWithIcon(
         IDC_SHOW_MANAGEMENT_PAGE,
-        chrome::GetManagedUiMenuItemLabel(browser_->profile()), icon);
+        chrome::GetManagedUiMenuItemLabel(browser_->profile()),
+        ui::ImageModel::FromVectorIcon(
+            vector_icons::kBusinessIcon,
+            ui::NativeTheme::kColorId_HighlightedMenuItemForegroundColor,
+            kIconSize));
   }
 #endif  // !defined(OS_CHROMEOS)
 
@@ -964,3 +979,23 @@
     const content::HostZoomMap::ZoomLevelChange& change) {
   UpdateZoomControls();
 }
+
+#if defined(OS_CHROMEOS)
+void AppMenuModel::UpdateSettingsItemState() {
+  const base::ListValue* system_features_disable_list_pref = nullptr;
+  PrefService* const local_state = g_browser_process->local_state();
+  if (local_state) {  // Sometimes it's not available in tests.
+    system_features_disable_list_pref =
+        local_state->GetList(policy::policy_prefs::kSystemFeaturesDisableList);
+  }
+
+  bool is_enabled = !system_features_disable_list_pref ||
+                    system_features_disable_list_pref->Find(
+                        base::Value(policy::SystemFeature::BROWSER_SETTINGS)) ==
+                        system_features_disable_list_pref->end();
+
+  int index = GetIndexOfCommandId(IDC_OPTIONS);
+  if (index != -1)
+    SetEnabledAt(index, is_enabled);
+}
+#endif  // defined(OS_CHROMEOS)
diff --git a/chrome/browser/ui/toolbar/app_menu_model.h b/chrome/browser/ui/toolbar/app_menu_model.h
index 74ea7c2e..b162d969 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.h
+++ b/chrome/browser/ui/toolbar/app_menu_model.h
@@ -12,6 +12,7 @@
 #include "base/time/time.h"
 #include "base/timer/elapsed_timer.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
+#include "components/prefs/pref_change_registrar.h"
 #include "content/public/browser/host_zoom_map.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "ui/base/accelerators/accelerator.h"
@@ -195,6 +196,12 @@
   // took to select the command.
   void LogMenuMetrics(int command_id);
 
+#if defined(OS_CHROMEOS)
+  // Disables/Enables the settings item based on kSystemFeaturesDisableList
+  // pref.
+  void UpdateSettingsItemState();
+#endif  // defined(OS_CHROMEOS)
+
   // Time menu has been open. Used by LogMenuMetrics() to record the time
   // to action when the user selects a menu item.
   base::ElapsedTimer timer_;
@@ -225,6 +232,8 @@
   std::unique_ptr<content::HostZoomMap::Subscription>
       browser_zoom_subscription_;
 
+  PrefChangeRegistrar local_state_pref_change_registrar_;
+
   DISALLOW_COPY_AND_ASSIGN(AppMenuModel);
 };
 
diff --git a/chrome/browser/ui/toolbar/app_menu_model_unittest.cc b/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
index 94c9c3f..94e10115 100644
--- a/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
@@ -24,6 +24,11 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/color_palette.h"
 
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#endif  // defined(OS_CHROMEOS)
+
 namespace {
 
 // Error class has a menu item.
@@ -217,3 +222,30 @@
   model.ActivatedAt(index2);
   EXPECT_EQ(1, error1->execute_count());
 }
+
+#if defined(OS_CHROMEOS)
+// Tests settings menu items is disabled in the app menu when
+// kSystemFeaturesDisableList is set.
+TEST_F(AppMenuModelTest, DisableSettingsItem) {
+  AppMenuModel model(this, browser());
+  model.Init();
+  int index = model.GetIndexOfCommandId(IDC_OPTIONS);
+  EXPECT_TRUE(model.IsEnabledAt(index));
+
+  {
+    ListPrefUpdate update(TestingBrowserProcess::GetGlobal()->local_state(),
+                          policy::policy_prefs::kSystemFeaturesDisableList);
+    base::ListValue* list = update.Get();
+    list->Append(policy::SystemFeature::BROWSER_SETTINGS);
+  }
+  EXPECT_FALSE(model.IsEnabledAt(index));
+
+  {
+    ListPrefUpdate update(TestingBrowserProcess::GetGlobal()->local_state(),
+                          policy::policy_prefs::kSystemFeaturesDisableList);
+    base::ListValue* list = update.Get();
+    list->Clear();
+  }
+  EXPECT_TRUE(model.IsEnabledAt(index));
+}
+#endif  // defined(OS_CHROMEOS)
diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model.cc b/chrome/browser/ui/toolbar/back_forward_menu_model.cc
index 15c60a3d..fc67b50 100644
--- a/chrome/browser/ui/toolbar/back_forward_menu_model.cc
+++ b/chrome/browser/ui/toolbar/back_forward_menu_model.cc
@@ -31,6 +31,7 @@
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "ui/base/accelerators/menu_label_accelerator_util.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/window_open_disposition.h"
 #include "ui/gfx/text_elider.h"
@@ -126,17 +127,18 @@
   return false;
 }
 
-bool BackForwardMenuModel::GetIconAt(int index, gfx::Image* icon) const {
+ui::ImageModel BackForwardMenuModel::GetIconAt(int index) const {
   if (!ItemHasIcon(index))
-    return false;
+    return ui::ImageModel();
 
   if (index == GetItemCount() - 1) {
-    *icon = ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
-        IDR_HISTORY_FAVICON);
+    return ui::ImageModel::FromImage(
+        ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
+            IDR_HISTORY_FAVICON));
   } else {
     NavigationEntry* entry = GetNavigationEntry(index);
-    *icon = entry->GetFavicon().image;
-    if (!entry->GetFavicon().valid && menu_model_delegate()) {
+    content::FaviconStatus fav_icon = entry->GetFavicon();
+    if (!fav_icon.valid && menu_model_delegate()) {
       // FetchFavicon is not const because it caches the result, but GetIconAt
       // is const because it is not be apparent to outside observers that an
       // internal change is taking place. Compared to spreading const in
@@ -145,9 +147,8 @@
       // this const_cast is the lesser evil.
       const_cast<BackForwardMenuModel*>(this)->FetchFavicon(entry);
     }
+    return ui::ImageModel::FromImage(fav_icon.image);
   }
-
-  return true;
 }
 
 ui::ButtonMenuItemModel* BackForwardMenuModel::GetButtonMenuItemAt(
diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model.h b/chrome/browser/ui/toolbar/back_forward_menu_model.h
index 0033938c..a93b2dd 100644
--- a/chrome/browser/ui/toolbar/back_forward_menu_model.h
+++ b/chrome/browser/ui/toolbar/back_forward_menu_model.h
@@ -27,10 +27,6 @@
 class WebContents;
 }
 
-namespace gfx {
-class Image;
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 //
 // BackForwardMenuModel
@@ -62,7 +58,7 @@
   bool GetAcceleratorAt(int index, ui::Accelerator* accelerator) const override;
   bool IsItemCheckedAt(int index) const override;
   int GetGroupIdAt(int index) const override;
-  bool GetIconAt(int index, gfx::Image* icon) const override;
+  ui::ImageModel GetIconAt(int index) const override;
   ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override;
   bool IsEnabledAt(int index) const override;
   MenuModel* GetSubmenuModelAt(int index) const override;
diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc b/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc
index 1c53b55..ed2d0b7 100644
--- a/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc
@@ -29,6 +29,7 @@
 #include "content/public/test/web_contents_tester.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/codec/png_codec.h"
 
 using base::ASCIIToUTF16;
@@ -526,8 +527,7 @@
 
   // Will return the current icon (default) but start an anync call
   // to retrieve the favicon from the favicon service.
-  gfx::Image default_icon;
-  back_model.GetIconAt(0, &default_icon);
+  ui::ImageModel default_icon = back_model.GetIconAt(0);
 
   // Make the favicon service run GetFavIconForURL,
   // FaviconDelegate.OnIconChanged will be called.
@@ -537,12 +537,11 @@
   EXPECT_TRUE(favicon_delegate.was_called());
 
   // Verify the bitmaps match.
-  gfx::Image valid_icon;
   // This time we will get the new favicon returned.
-  back_model.GetIconAt(0, &valid_icon);
+  ui::ImageModel valid_icon = back_model.GetIconAt(0);
 
-  SkBitmap default_icon_bitmap = *default_icon.ToSkBitmap();
-  SkBitmap valid_icon_bitmap = *valid_icon.ToSkBitmap();
+  SkBitmap default_icon_bitmap = *default_icon.GetImage().ToSkBitmap();
+  SkBitmap valid_icon_bitmap = *valid_icon.GetImage().ToSkBitmap();
 
   // Verify we did not get the default favicon.
   EXPECT_NE(
diff --git a/chrome/browser/ui/toolbar/media_router_contextual_menu.cc b/chrome/browser/ui/toolbar/media_router_contextual_menu.cc
index b9d4ac81..6b1d09b2 100644
--- a/chrome/browser/ui/toolbar/media_router_contextual_menu.cc
+++ b/chrome/browser/ui/toolbar/media_router_contextual_menu.cc
@@ -31,6 +31,7 @@
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
 #include "extensions/common/constants.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/models/menu_model_delegate.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -60,10 +61,11 @@
   if (shown_by_policy) {
     menu_model_->AddItemWithStringId(IDC_MEDIA_ROUTER_SHOWN_BY_POLICY,
                                      IDS_MEDIA_ROUTER_SHOWN_BY_POLICY);
+    // TODO (kylixrd): Review the use of the hard-coded color constant.
     menu_model_->SetIcon(
         menu_model_->GetIndexOfCommandId(IDC_MEDIA_ROUTER_SHOWN_BY_POLICY),
-        gfx::Image(gfx::CreateVectorIcon(vector_icons::kBusinessIcon, 16,
-                                         gfx::kChromeIconGrey)));
+        ui::ImageModel::FromVectorIcon(vector_icons::kBusinessIcon,
+                                       gfx::kChromeIconGrey, 16));
   } else {
     menu_model_->AddCheckItemWithStringId(
         IDC_MEDIA_ROUTER_ALWAYS_SHOW_TOOLBAR_ACTION,
diff --git a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc
index fef58eae..1ff04b7 100644
--- a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc
+++ b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc
@@ -42,6 +42,7 @@
 #include "components/sync_sessions/synced_session.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -125,12 +126,9 @@
   return command_id - kFirstLocalWindowCommandId;
 }
 
-gfx::Image CreateFavicon(const gfx::VectorIcon& icon) {
-  ui::NativeTheme* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
-  return gfx::Image(
-      gfx::CreateVectorIcon(icon, 16,
-                            native_theme->GetSystemColor(
-                                ui::NativeTheme::kColorId_DefaultIconColor)));
+ui::ImageModel CreateFavicon(const gfx::VectorIcon& icon) {
+  return ui::ImageModel::FromVectorIcon(
+      icon, ui::NativeTheme::kColorId_DefaultIconColor, 16);
 }
 
 }  // namespace
@@ -564,7 +562,8 @@
   int index_in_menu = GetIndexOfCommandId(command_id);
 
   // Set default icon first.
-  SetIcon(index_in_menu, favicon::GetDefaultFavicon());
+  SetIcon(index_in_menu,
+          ui::ImageModel::FromImage(favicon::GetDefaultFavicon()));
 
   bool is_local_tab = command_id < kFirstOtherDevicesTabCommandId;
   if (is_local_tab) {
@@ -609,7 +608,7 @@
   }
   int index_in_menu = GetIndexOfCommandId(command_id);
   DCHECK_GT(index_in_menu, -1);
-  SetIcon(index_in_menu, image_result.image);
+  SetIcon(index_in_menu, ui::ImageModel::FromImage(image_result.image));
   ui::MenuModelDelegate* delegate = menu_model_delegate();
   if (delegate)
     delegate->OnIconChanged(index_in_menu);
diff --git a/chrome/browser/ui/views/accessibility/caption_bubble.cc b/chrome/browser/ui/views/accessibility/caption_bubble.cc
index 8dbadb2..1cae946 100644
--- a/chrome/browser/ui/views/accessibility/caption_bubble.cc
+++ b/chrome/browser/ui/views/accessibility/caption_bubble.cc
@@ -13,6 +13,19 @@
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/bubble/bubble_frame_view.h"
 #include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/flex_layout.h"
+#include "ui/views/layout/flex_layout_types.h"
+#include "ui/views/layout/layout_types.h"
+#include "ui/views/view_class_properties.h"
+
+namespace {
+// Formatting constants
+static constexpr int kLineHeightDip = 18;
+static constexpr int kMaxHeightDip = kLineHeightDip * 2;
+static constexpr int kCornerRadiusDip = 8;
+static constexpr double kPreferredAnchorWidthPercentage = 0.8;
+// Dark grey at 80% opacity.
+static constexpr SkColor kCaptionBubbleColor = SkColorSetARGB(204, 30, 30, 30);
 
 // CaptionBubble implementation of BubbleFrameView.
 class CaptionBubbleFrameView : public views::BubbleFrameView {
@@ -39,6 +52,7 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(CaptionBubbleFrameView);
 };
+}  // namespace
 
 namespace captions {
 
@@ -55,33 +69,31 @@
 CaptionBubble::~CaptionBubble() = default;
 
 void CaptionBubble::Init() {
-  SetLayoutManager(std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kVertical, gfx::Insets(10)));
-  set_color(SK_ColorGRAY);
+  auto* layout = SetLayoutManager(std::make_unique<views::FlexLayout>());
+  layout->SetOrientation(views::LayoutOrientation::kVertical);
+  layout->SetMainAxisAlignment(views::LayoutAlignment::kEnd);
+  layout->SetDefault(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
+                               views::MaximumFlexSizeRule::kPreferred,
+                               /*adjust_height_for_width*/ true));
+
+  set_color(kCaptionBubbleColor);
   set_close_on_deactivate(false);
 
   label_.SetMultiLine(true);
-  label_.SetMaxLines(2);
-  int max_width = GetAnchorView()->width() * 0.8;
+  int max_width = GetAnchorView()->width() * kPreferredAnchorWidthPercentage;
   label_.SetMaximumWidth(max_width);
   label_.SetEnabledColor(SK_ColorWHITE);
   label_.SetBackgroundColor(SK_ColorTRANSPARENT);
   label_.SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
-  label_.SetLineHeight(18);
+  label_.SetLineHeight(kLineHeightDip);
 
   std::vector<std::string> font_names = {"Arial", "Helvetica"};
-  gfx::FontList* font_list = new gfx::FontList(
-      font_names, gfx::Font::FontStyle::NORMAL, 14, gfx::Font::Weight::NORMAL);
-  label_.SetFontList(*font_list);
+  label_.SetFontList(gfx::FontList(font_names, gfx::Font::FontStyle::NORMAL, 14,
+                                   gfx::Font::Weight::NORMAL));
 
-  // Add some dummy text while this is in development.
-  std::string text =
-      "Taylor Alison Swift (born December 13, 1989) is an American "
-      "singer-songwriter. She is known for narrative songs about her personal "
-      "life, which have received widespread media coverage. At age 14, Swift "
-      "became the youngest artist signed by the Sony/ATV Music publishing "
-      "house and, at age 15, she signed her first record deal.";
-  label_.SetText(base::ASCIIToUTF16(text));
+  SetPreferredSize(gfx::Size(max_width, kMaxHeightDip));
 
   AddChildView(&label_);
 }
@@ -96,7 +108,7 @@
   auto border = std::make_unique<views::BubbleBorder>(
       views::BubbleBorder::FLOAT, views::BubbleBorder::NO_SHADOW,
       gfx::kPlaceholderColor);
-  border->SetCornerRadius(2);
+  border->SetCornerRadius(kCornerRadiusDip);
   frame->SetBubbleBorder(std::move(border));
   return frame;
 }
diff --git a/chrome/browser/ui/views/accessibility/caption_bubble.h b/chrome/browser/ui/views/accessibility/caption_bubble.h
index 878fabf..f940aa5 100644
--- a/chrome/browser/ui/views/accessibility/caption_bubble.h
+++ b/chrome/browser/ui/views/accessibility/caption_bubble.h
@@ -36,6 +36,7 @@
       views::Widget* widget) override;
 
  private:
+  friend class CaptionBubbleControllerViewsTest;
   views::Label label_;
   base::ScopedClosureRunner destroyed_callback_;
 };
diff --git a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.h b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.h
index 9b498a7..ecc060041 100644
--- a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.h
+++ b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.h
@@ -39,6 +39,7 @@
   void OnActiveTabChanged(int index) override;
 
  private:
+  friend class CaptionBubbleControllerViewsTest;
   // A callback passed to the CaptionBubble which is called when the
   // CaptionBubble is destroyed.
   void OnCaptionBubbleDestroyed();
diff --git a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc
new file mode 100644
index 0000000..13a62fe
--- /dev/null
+++ b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 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 "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ui/views/accessibility/caption_bubble_controller_views.h"
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/views/accessibility/caption_bubble.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "ui/views/widget/widget.h"
+
+namespace captions {
+
+class CaptionBubbleControllerViewsTest : public InProcessBrowserTest {
+ public:
+  CaptionBubbleControllerViewsTest() = default;
+  ~CaptionBubbleControllerViewsTest() override = default;
+  CaptionBubbleControllerViewsTest(const CaptionBubbleControllerViewsTest&) =
+      delete;
+  CaptionBubbleControllerViewsTest& operator=(
+      const CaptionBubbleControllerViewsTest&) = delete;
+
+  CaptionBubbleControllerViews* GetController() {
+    if (!controller_)
+      controller_ = std::make_unique<CaptionBubbleControllerViews>(browser());
+    return controller_.get();
+  }
+
+  CaptionBubble* GetBubble() {
+    return controller_ ? controller_->caption_bubble_ : nullptr;
+  }
+
+  views::Label* GetLabel() {
+    return controller_ ? &controller_->caption_bubble_->label_ : nullptr;
+  }
+
+  std::string GetLabelText() {
+    return controller_ ? base::UTF16ToUTF8(GetLabel()->GetText()) : "";
+  }
+
+  views::Widget* GetCaptionWidget() {
+    return controller_ ? controller_->caption_widget_ : nullptr;
+  }
+
+ private:
+  std::unique_ptr<CaptionBubbleControllerViews> controller_;
+};
+
+IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest, ShowsCaptionInBubble) {
+  GetController()->OnCaptionReceived("Taylor");
+  EXPECT_TRUE(GetCaptionWidget()->IsVisible());
+  EXPECT_EQ("Taylor", GetLabelText());
+  GetController()->OnCaptionReceived(
+      "Taylor Alison Swift (born December 13, "
+      "1989)");
+  EXPECT_EQ("Taylor Alison Swift (born December 13, 1989)", GetLabelText());
+
+  // Hides the bubble when set to the empty string.
+  GetController()->OnCaptionReceived("");
+  EXPECT_FALSE(GetCaptionWidget()->IsVisible());
+
+  // Shows it again when the caption is no longer empty.
+  GetController()->OnCaptionReceived(
+      "Taylor Alison Swift (born December 13, "
+      "1989) is an American singer-songwriter.");
+  EXPECT_TRUE(GetCaptionWidget()->IsVisible());
+  EXPECT_EQ(
+      "Taylor Alison Swift (born December 13, 1989) is an American "
+      "singer-songwriter.",
+      GetLabelText());
+}
+
+IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest, LaysOutCaptionLabel) {
+  // A short caption is bottom-aligned with the bubble.
+  GetController()->OnCaptionReceived("Cats rock");
+  EXPECT_EQ(GetLabel()->GetBoundsInScreen().bottom(),
+            GetBubble()->GetBoundsInScreen().bottom());
+
+  // Ensure overflow by using a very long caption, should still be aligned
+  // with the bottom of the bubble.
+  GetController()->OnCaptionReceived(
+      "Taylor Alison Swift (born December 13, 1989) is an American "
+      "singer-songwriter. She is known for narrative songs about her personal "
+      "life, which have received widespread media coverage. At age 14, Swift "
+      "became the youngest artist signed by the Sony/ATV Music publishing "
+      "house and, at age 15, she signed her first record deal.");
+  EXPECT_EQ(GetLabel()->GetBoundsInScreen().bottom(),
+            GetBubble()->GetBoundsInScreen().bottom());
+}
+
+}  // namespace captions
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc b/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc
index d5bbf80..1ad36dd 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc
@@ -305,6 +305,7 @@
     // the WidgetDelegate provides |bb_view_| as the contents view and adds it
     // to the hierarchy.
     ViewEventTestBase::SetUp();
+    ASSERT_TRUE(bb_view_);
 
     // Verify the layout triggered by the initial size preserves the overflow
     // state calculated in GetPreferredSizeForContents().
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc
index ff43d7e..31ed305 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc
@@ -57,8 +57,6 @@
 views::Widget* BookmarkBubbleView::ShowBubble(
     views::View* anchor_view,
     views::Button* highlighted_button,
-    const gfx::Rect& anchor_rect,
-    gfx::NativeView parent_window,
     bookmarks::BookmarkBubbleObserver* observer,
     std::unique_ptr<BubbleSyncPromoDelegate> delegate,
     Profile* profile,
@@ -70,14 +68,6 @@
   bookmark_bubble_ =
       new BookmarkBubbleView(anchor_view, observer, std::move(delegate),
                              profile, url, !already_bookmarked);
-  // Bookmark bubble should always anchor TOP_RIGHT, but the
-  // LocationBarBubbleDelegateView does not know that and may use different
-  // arrow anchoring.
-  bookmark_bubble_->SetArrow(views::BubbleBorder::TOP_RIGHT);
-  if (!anchor_view) {
-    bookmark_bubble_->SetAnchorRect(anchor_rect);
-    bookmark_bubble_->set_parent_window(parent_window);
-  }
   if (highlighted_button)
     bookmark_bubble_->SetHighlightedButton(highlighted_button);
   views::Widget* bubble_widget =
@@ -229,6 +219,9 @@
       profile_(profile),
       url_(url),
       newly_bookmarked_(newly_bookmarked) {
+  DCHECK(anchor_view);
+
+  SetArrow(views::BubbleBorder::TOP_RIGHT);
   DialogDelegate::SetButtonLabel(ui::DIALOG_BUTTON_OK,
                                    l10n_util::GetStringUTF16(IDS_DONE));
   DialogDelegate::SetButtonLabel(
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.h b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.h
index 297df16..e5da2b23 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.h
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.h
@@ -42,8 +42,6 @@
   static views::Widget* ShowBubble(
       views::View* anchor_view,
       views::Button* highlighted_button,
-      const gfx::Rect& anchor_rect,
-      gfx::NativeView parent_window,
       bookmarks::BookmarkBubbleObserver* observer,
       std::unique_ptr<BubbleSyncPromoDelegate> delegate,
       Profile* profile,
diff --git a/chrome/browser/ui/views/menu_model_adapter_test.cc b/chrome/browser/ui/views/menu_model_adapter_test.cc
index 9860c8e5..73b126d 100644
--- a/chrome/browser/ui/views/menu_model_adapter_test.cc
+++ b/chrome/browser/ui/views/menu_model_adapter_test.cc
@@ -63,7 +63,9 @@
 
   int GetGroupIdAt(int index) const override { return 0; }
 
-  bool GetIconAt(int index, gfx::Image* icon) const override { return false; }
+  ui::ImageModel GetIconAt(int index) const override {
+    return ui::ImageModel();
+  }
 
   ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override {
     return nullptr;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index af38122..8e05b13 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -59,6 +59,7 @@
 #include "ui/base/ime/text_input_client.h"
 #include "ui/base/ime/text_input_type.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/models/simple_menu_model.h"
 #include "ui/compositor/layer.h"
 #include "ui/events/event.h"
@@ -1862,7 +1863,8 @@
           send_tab_to_self_sub_menu_model_.get());
     }
 #if !defined(OS_MACOSX)
-    menu_contents->SetIcon(index, kSendTabToSelfIcon);
+    menu_contents->SetIcon(index,
+                           ui::ImageModel::FromVectorIcon(kSendTabToSelfIcon));
 #endif
     menu_contents->InsertSeparatorAt(++index, ui::NORMAL_SEPARATOR);
   }
diff --git a/chrome/browser/ui/views/session_crashed_bubble_view_browsertest.cc b/chrome/browser/ui/views/session_crashed_bubble_view_browsertest.cc
index d2fd3ed..037ed11 100644
--- a/chrome/browser/ui/views/session_crashed_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/session_crashed_bubble_view_browsertest.cc
@@ -66,7 +66,9 @@
   ShowAndVerifyUi();
 }
 
-#if defined(OS_LINUX)
+// TODO(https://crbug.com/1068579): Fails on Windows because the simulated key
+// events don't trigger the accelerators.
+#if !defined(OS_WIN)
 // Regression test for https://crbug.com/1042010, it should be possible to focus
 // the bubble with the "focus dialog" hotkey combination (Alt+Shift+A).
 IN_PROC_BROWSER_TEST_F(SessionCrashedBubbleViewTest,
diff --git a/chrome/browser/ui/views/status_icons/concat_menu_model.cc b/chrome/browser/ui/views/status_icons/concat_menu_model.cc
index ead2226..c6fb2f0 100644
--- a/chrome/browser/ui/views/status_icons/concat_menu_model.cc
+++ b/chrome/browser/ui/views/status_icons/concat_menu_model.cc
@@ -37,7 +37,7 @@
   return GetterImpl(&ui::MenuModel::GetMinorTextAt, index);
 }
 
-const gfx::VectorIcon* ConcatMenuModel::GetMinorIconAt(int index) const {
+ui::ImageModel ConcatMenuModel::GetMinorIconAt(int index) const {
   return GetterImpl(&ui::MenuModel::GetMinorIconAt, index);
 }
 
@@ -58,8 +58,8 @@
   return GetterImpl(&ui::MenuModel::GetGroupIdAt, index);
 }
 
-bool ConcatMenuModel::GetIconAt(int index, gfx::Image* icon) const {
-  return GetterImpl(&ui::MenuModel::GetGroupIdAt, index);
+ui::ImageModel ConcatMenuModel::GetIconAt(int index) const {
+  return GetterImpl(&ui::MenuModel::GetIconAt, index);
 }
 
 ui::ButtonMenuItemModel* ConcatMenuModel::GetButtonMenuItemAt(int index) const {
diff --git a/chrome/browser/ui/views/status_icons/concat_menu_model.h b/chrome/browser/ui/views/status_icons/concat_menu_model.h
index 0ad30d5..bad06f0 100644
--- a/chrome/browser/ui/views/status_icons/concat_menu_model.h
+++ b/chrome/browser/ui/views/status_icons/concat_menu_model.h
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/macros.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/models/menu_model.h"
 
 // Combines two menu models (without using submenus).
@@ -24,12 +25,12 @@
   int GetCommandIdAt(int index) const override;
   base::string16 GetLabelAt(int index) const override;
   base::string16 GetMinorTextAt(int index) const override;
-  const gfx::VectorIcon* GetMinorIconAt(int index) const override;
+  ui::ImageModel GetMinorIconAt(int index) const override;
   bool IsItemDynamicAt(int index) const override;
   bool GetAcceleratorAt(int index, ui::Accelerator* accelerator) const override;
   bool IsItemCheckedAt(int index) const override;
   int GetGroupIdAt(int index) const override;
-  bool GetIconAt(int index, gfx::Image* icon) const override;
+  ui::ImageModel GetIconAt(int index) const override;
   ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override;
   bool IsEnabledAt(int index) const override;
   bool IsVisibleAt(int index) const override;
diff --git a/chrome/browser/ui/views/status_icons/status_icon_linux_wrapper.h b/chrome/browser/ui/views/status_icons/status_icon_linux_wrapper.h
index 6856129..839b689 100644
--- a/chrome/browser/ui/views/status_icons/status_icon_linux_wrapper.h
+++ b/chrome/browser/ui/views/status_icons/status_icon_linux_wrapper.h
@@ -11,6 +11,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "chrome/browser/status_icons/desktop_notification_balloon.h"
 #include "chrome/browser/status_icons/status_icon.h"
+#include "ui/gfx/image/image_skia.h"
 #include "ui/views/linux_ui/status_icon_linux.h"
 
 class StatusIconLinuxDbus;
diff --git a/chrome/browser/ui/views/status_icons/status_icon_win.cc b/chrome/browser/ui/views/status_icons/status_icon_win.cc
index e6fad2e..3d07e8c1 100644
--- a/chrome/browser/ui/views/status_icons/status_icon_win.cc
+++ b/chrome/browser/ui/views/status_icons/status_icon_win.cc
@@ -12,6 +12,7 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/icon_util.h"
+#include "ui/gfx/image/image_skia.h"
 #include "ui/message_center/public/cpp/notifier_id.h"
 #include "ui/views/controls/menu/menu_runner.h"
 
@@ -22,11 +23,7 @@
                              UINT id,
                              HWND window,
                              UINT message)
-    : tray_(tray),
-      icon_id_(id),
-      window_(window),
-      message_id_(message),
-      menu_model_(NULL) {
+    : tray_(tray), icon_id_(id), window_(window), message_id_(message) {
   NOTIFYICONDATA icon_data;
   InitIconData(&icon_data);
   icon_data.uFlags = NIF_MESSAGE;
diff --git a/chrome/browser/ui/views/status_icons/status_icon_win.h b/chrome/browser/ui/views/status_icons/status_icon_win.h
index 05e45bc..8c567b4 100644
--- a/chrome/browser/ui/views/status_icons/status_icon_win.h
+++ b/chrome/browser/ui/views/status_icons/status_icon_win.h
@@ -81,7 +81,7 @@
   base::win::ScopedHICON balloon_icon_;
 
   // Not owned.
-  ui::MenuModel* menu_model_;
+  ui::MenuModel* menu_model_ = nullptr;
 
   // Context menu associated with this icon (if any).
   std::unique_ptr<views::MenuRunner> menu_runner_;
diff --git a/chrome/browser/ui/views/test/view_event_test_base.h b/chrome/browser/ui/views/test/view_event_test_base.h
index f56a53c9..1820acf 100644
--- a/chrome/browser/ui/views/test/view_event_test_base.h
+++ b/chrome/browser/ui/views/test/view_event_test_base.h
@@ -72,7 +72,7 @@
 
   static void SetUpTestCase();
 
-  // testing::Test:
+  // ChromeViewsTestBase:
   void SetUp() override;
   void TearDown() override;
 
diff --git a/chrome/browser/ui/views/toolbar/app_menu.cc b/chrome/browser/ui/views/toolbar/app_menu.cc
index 9244eb4e..e7dcf3ae 100644
--- a/chrome/browser/ui/views/toolbar/app_menu.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu.cc
@@ -48,6 +48,7 @@
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/layout.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/font_list.h"
@@ -56,6 +57,7 @@
 #include "ui/gfx/scoped_canvas.h"
 #include "ui/gfx/skia_util.h"
 #include "ui/gfx/text_utils.h"
+#include "ui/native_theme/themed_vector_icon.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/button/image_button.h"
@@ -673,9 +675,12 @@
     int command_id = model_->GetCommandIdAt(index);
     views::MenuItemView* item = menu_item_->GetMenuItemByID(command_id);
     DCHECK(item);
-    gfx::Image icon;
-    model_->GetIconAt(index, &icon);
-    item->SetIcon(*icon.ToImageSkia());
+    ui::ImageModel image = model_->GetIconAt(index);
+    // TODO (kylixrd): Use a utility function to get this as an actual image.
+    if (image.IsImage())
+      item->SetIcon(*image.GetImage().ToImageSkia());
+    else if (image.IsVectorIcon())
+      item->SetIcon(ui::ThemedVectorIcon(image.GetVectorIcon()));
   }
 
   void OnMenuStructureChanged() override {
@@ -1118,9 +1123,11 @@
     menu_item->SetVisible(model->IsVisibleAt(model_index));
 
     if (menu_type == MenuModel::TYPE_COMMAND && model->HasIcons()) {
-      gfx::Image icon;
-      if (model->GetIconAt(model_index, &icon))
-        menu_item->SetIcon(*icon.ToImageSkia());
+      ui::ImageModel icon = model->GetIconAt(model_index);
+      if (icon.IsImage())
+        menu_item->SetIcon(*icon.GetImage().ToImageSkia());
+      else if (icon.IsVectorIcon())
+        menu_item->SetIcon(ui::ThemedVectorIcon(icon.GetVectorIcon()));
     }
 
     // If we want to show items relating to reopening the last-closed tab as
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index 229a563..c5ca47a 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -422,9 +422,9 @@
   // ChromeOS does not show the signin promo.
   delegate.reset(new BookmarkBubbleSignInDelegate(browser_));
 #endif
-  BookmarkBubbleView::ShowBubble(anchor_view, bookmark_star_icon, gfx::Rect(),
-                                 nullptr, observer, std::move(delegate),
-                                 browser_->profile(), url, already_bookmarked);
+  BookmarkBubbleView::ShowBubble(anchor_view, bookmark_star_icon, observer,
+                                 std::move(delegate), browser_->profile(), url,
+                                 already_bookmarked);
 }
 
 ExtensionsToolbarButton* ToolbarView::GetExtensionsButton() const {
diff --git a/chrome/browser/ui/web_applications/web_app_menu_model.cc b/chrome/browser/ui/web_applications/web_app_menu_model.cc
index f91c919..df965da 100644
--- a/chrome/browser/ui/web_applications/web_app_menu_model.cc
+++ b/chrome/browser/ui/web_applications/web_app_menu_model.cc
@@ -15,6 +15,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/strings/grit/components_strings.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "url/gurl.h"
 
 constexpr int WebAppMenuModel::kUninstallAppCommandId;
@@ -37,7 +38,8 @@
                                        ->GetActiveWebContents()
                                        ->GetVisibleURL()));
   SetMinorIcon(app_info_index,
-               browser()->location_bar_model()->GetVectorIcon());
+               ui::ImageModel::FromVectorIcon(
+                   browser()->location_bar_model()->GetVectorIcon()));
 
   AddSeparator(ui::NORMAL_SEPARATOR);
   AddItemWithStringId(IDC_COPY_URL, IDS_COPY_URL);
diff --git a/chrome/browser/ui/webauthn/other_transports_menu_model.cc b/chrome/browser/ui/webauthn/other_transports_menu_model.cc
index 85110b44..1cb76082 100644
--- a/chrome/browser/ui/webauthn/other_transports_menu_model.cc
+++ b/chrome/browser/ui/webauthn/other_transports_menu_model.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/ui/webauthn/transport_utils.h"
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/paint_vector_icon.h"
 
@@ -15,6 +16,8 @@
 
 gfx::ImageSkia GetTransportIcon(AuthenticatorTransport transport) {
   constexpr int kTransportIconSize = 16;
+  // TODO (kylixrd): Review the use of the hard-coded color for possible change
+  // to using a ColorProvider color id.
   return gfx::CreateVectorIcon(*GetTransportVectorIcon(transport),
                                kTransportIconSize, gfx::kGoogleGrey700);
 }
@@ -63,7 +66,7 @@
     auto name = GetTransportHumanReadableName(
         transport, TransportSelectionContext::kOtherTransportsMenu);
     AddItemWithIcon(base::strict_cast<int>(transport), std::move(name),
-                    GetTransportIcon(transport));
+                    ui::ImageModel::FromImageSkia(GetTransportIcon(transport)));
   }
 }
 
@@ -71,7 +74,8 @@
   AddItemWithIcon(
       base::strict_cast<int>(AuthenticatorTransport::kBluetoothLowEnergy),
       l10n_util::GetStringUTF16(IDS_WEBAUTHN_TRANSPORT_POPUP_ANOTHER_BLE),
-      GetTransportIcon(AuthenticatorTransport::kBluetoothLowEnergy));
+      ui::ImageModel::FromImageSkia(
+          GetTransportIcon(AuthenticatorTransport::kBluetoothLowEnergy)));
 }
 
 #if defined(OS_WIN)
@@ -80,11 +84,11 @@
 constexpr int kWinNativeApiMenuCommand = 999;
 
 void OtherTransportsMenuModel::AppendItemForNativeWinApi() {
-  AddItemWithIcon(
-      kWinNativeApiMenuCommand,
-      l10n_util::GetStringUTF16(
-          IDS_WEBAUTHN_TRANSPORT_POPUP_DIFFERENT_AUTHENTICATOR_WIN),
-      GetTransportIcon(AuthenticatorTransport::kUsbHumanInterfaceDevice));
+  AddItemWithIcon(kWinNativeApiMenuCommand,
+                  l10n_util::GetStringUTF16(
+                      IDS_WEBAUTHN_TRANSPORT_POPUP_DIFFERENT_AUTHENTICATOR_WIN),
+                  ui::ImageModel::FromImageSkia(GetTransportIcon(
+                      AuthenticatorTransport::kUsbHumanInterfaceDevice)));
 }
 #endif  // defined(OS_WIN)
 
diff --git a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_ui.cc b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_ui.cc
index fde562c2..8d195a2 100644
--- a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_ui.cc
@@ -53,6 +53,7 @@
       {"prechecksFailedTitle", IDS_CROSTINI_UPGRADER_PRECHECKS_FAILED_TITLE},
       {"upgradingTitle", IDS_CROSTINI_UPGRADER_UPGRADING_TITLE},
       {"restoreTitle", IDS_CROSTINI_UPGRADER_RESTORE_TITLE},
+      {"restoreSucceededTitle", IDS_CROSTINI_UPGRADER_RESTORE_SUCCEEDED_TITLE},
       {"succeededTitle", IDS_CROSTINI_UPGRADER_SUCCEEDED_TITLE},
       {"cancelingTitle", IDS_CROSTINI_UPGRADER_CANCELING_TITLE},
       {"errorTitle", IDS_CROSTINI_UPGRADER_ERROR_TITLE},
@@ -69,6 +70,8 @@
       {"cancelingMessage", IDS_CROSTINI_UPGRADER_CANCELING},
       {"offerRestoreMessage", IDS_CROSTINI_UPGRADER_OFFER_RESTORE_MESSAGE},
       {"restoreMessage", IDS_CROSTINI_UPGRADER_RESTORE_MESSAGE},
+      {"restoreSucceededMessage",
+       IDS_CROSTINI_UPGRADER_RESTORE_SUCCEEDED_MESSAGE},
 
       {"backupCheckboxMessage", IDS_CROSTINI_UPGRADER_BACKUP_CHECKBOX_MESSAGE},
       {"backupChangeLocation", IDS_CROSTINI_UPGRADER_BACKUP_CHANGE_LOCATION},
diff --git a/chrome/browser/ui/webui/chromeos/drive_internals_ui.cc b/chrome/browser/ui/webui/chromeos/drive_internals_ui.cc
index 39b9f40b..e3fc2d7 100644
--- a/chrome/browser/ui/webui/chromeos/drive_internals_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/drive_internals_ui.cc
@@ -555,8 +555,8 @@
         GetIntegrationService();
     if (integration_service) {
       integration_service->ClearCacheAndRemountFileSystem(
-          base::Bind(&DriveInternalsWebUIHandler::ResetFinished,
-                     weak_ptr_factory_.GetWeakPtr()));
+          base::BindOnce(&DriveInternalsWebUIHandler::ResetFinished,
+                         weak_ptr_factory_.GetWeakPtr()));
     }
   }
 
diff --git a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
index f6bd2f3..4b30e07 100644
--- a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
+++ b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
@@ -953,9 +953,10 @@
   std::string extension_id;
   CHECK(args->GetString(0, &extension_id));
 
-  if (DesktopPWAsWithoutExtensions() &&
-      web_app_provider_->registrar().IsInstalled(extension_id)) {
-    NOTIMPLEMENTED();
+  if (web_app_provider_->registrar().IsInstalled(extension_id)) {
+    chrome::ShowSiteSettings(
+        chrome::FindBrowserWithWebContents(web_ui()->GetWebContents()),
+        web_app_provider_->registrar().GetAppLaunchURL(extension_id));
     return;
   }
 
@@ -967,13 +968,7 @@
                                  extensions::ExtensionRegistry::TERMINATED);
   if (!extension)
     return;
-
-  if (extension->is_hosted_app() && extension->from_bookmark()) {
-    chrome::ShowSiteSettings(
-        chrome::FindBrowserWithWebContents(web_ui()->GetWebContents()),
-        extensions::AppLaunchInfo::GetFullLaunchURL(extension));
-    return;
-  }
+  DCHECK(!extension->from_bookmark());
 
   UMA_HISTOGRAM_ENUMERATION("Apps.AppInfoDialog.Launches",
                             AppInfoLaunchSource::FROM_APPS_PAGE,
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.cc
index fcde235..41a5387 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.cc
@@ -50,7 +50,7 @@
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/locale_settings.h"
-#include "chrome/services/local_search_service/public/mojom/types.mojom.h"
+#include "chrome/services/local_search_service/local_search_service_impl.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/services/assistant/public/features.h"
@@ -78,9 +78,9 @@
 namespace settings {
 namespace {
 
-std::vector<local_search_service::mojom::DataPtr> ConceptVectorToDataPtrVector(
+std::vector<local_search_service::Data> ConceptVectorToDataVector(
     const std::vector<SearchConcept>& tags_group) {
-  std::vector<local_search_service::mojom::DataPtr> data_list;
+  std::vector<local_search_service::Data> data_list;
 
   for (const auto& concept : tags_group) {
     std::vector<base::string16> search_tags;
@@ -99,8 +99,8 @@
 
     // Note: A stringified version of the canonical tag message ID is used as
     // the identifier for this search data.
-    data_list.push_back(local_search_service::mojom::Data::New(
-        base::NumberToString(concept.canonical_message_id), search_tags));
+    data_list.emplace_back(base::NumberToString(concept.canonical_message_id),
+                           search_tags);
   }
 
   return data_list;
@@ -2071,11 +2071,9 @@
 
 OsSettingsLocalizedStringsProvider::OsSettingsLocalizedStringsProvider(
     Profile* profile,
-    local_search_service::mojom::LocalSearchService* local_search_service) {
-  local_search_service->GetIndex(
-      local_search_service::mojom::LocalSearchService::IndexId::CROS_SETTINGS,
-      index_remote_.BindNewPipeAndPassReceiver());
-
+    local_search_service::LocalSearchServiceImpl* local_search_service)
+    : index_(local_search_service->GetIndexImpl(
+          local_search_service::IndexId::kCrosSettings)) {
   // Add per-page string providers.
   // TODO(khorimoto): Add providers for the remaining pages.
   per_page_providers_.push_back(
@@ -2135,13 +2133,16 @@
 }
 
 void OsSettingsLocalizedStringsProvider::Shutdown() {
-  index_remote_.reset();
+  index_ = nullptr;
 }
 
 void OsSettingsLocalizedStringsProvider::AddSearchTags(
     const std::vector<SearchConcept>& tags_group) {
-  index_remote_->AddOrUpdate(ConceptVectorToDataPtrVector(tags_group),
-                             /*callback=*/base::DoNothing());
+  // Note: Can be null after Shutdown().
+  if (!index_)
+    return;
+
+  index_->AddOrUpdate(ConceptVectorToDataVector(tags_group));
 
   // Add each concept to the map. Note that it is safe to take the address of
   // each concept because all concepts are allocated via static
@@ -2152,13 +2153,17 @@
 
 void OsSettingsLocalizedStringsProvider::RemoveSearchTags(
     const std::vector<SearchConcept>& tags_group) {
+  // Note: Can be null after Shutdown().
+  if (!index_)
+    return;
+
   std::vector<std::string> ids;
   for (const auto& concept : tags_group) {
     canonical_id_to_metadata_map_.erase(concept.canonical_message_id);
     ids.push_back(base::NumberToString(concept.canonical_message_id));
   }
 
-  index_remote_->Delete(ids, /*callback=*/base::DoNothing());
+  index_->Delete(ids);
 }
 
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.h b/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.h
index c0d8c5a..f2dc518 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.h
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider.h
@@ -10,7 +10,7 @@
 #include <vector>
 
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_per_page_strings_provider.h"
-#include "chrome/services/local_search_service/public/mojom/local_search_service.mojom.h"
+#include "chrome/services/local_search_service/index_impl.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
@@ -20,6 +20,10 @@
 class WebUIDataSource;
 }  // namespace content
 
+namespace local_search_service {
+class LocalSearchServiceImpl;
+}  // namespace local_search_service
+
 namespace chromeos {
 namespace settings {
 
@@ -52,7 +56,7 @@
  public:
   OsSettingsLocalizedStringsProvider(
       Profile* profile,
-      local_search_service::mojom::LocalSearchService* local_search_service);
+      local_search_service::LocalSearchServiceImpl* local_search_service);
   OsSettingsLocalizedStringsProvider(
       const OsSettingsLocalizedStringsProvider& other) = delete;
   OsSettingsLocalizedStringsProvider& operator=(
@@ -81,7 +85,7 @@
 
   std::vector<std::unique_ptr<OsSettingsPerPageStringsProvider>>
       per_page_providers_;
-  mojo::Remote<local_search_service::mojom::Index> index_remote_;
+  local_search_service::IndexImpl* index_;
   std::unordered_map<int, const SearchConcept*> canonical_id_to_metadata_map_;
 };
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider_factory.cc b/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider_factory.cc
index 8375790..9884be6 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider_factory.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider_factory.cc
@@ -48,7 +48,7 @@
       profile,
       local_search_service::LocalSearchServiceProxyFactory::GetForProfile(
           Profile::FromBrowserContext(profile))
-          ->GetLocalSearchService());
+          ->GetLocalSearchServiceImpl());
 }
 
 bool OsSettingsLocalizedStringsProviderFactory::ServiceIsNULLWhileTesting()
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider_unittest.cc
index c8eb26b..04f40c3 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider_unittest.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_localized_strings_provider_unittest.cc
@@ -8,8 +8,8 @@
 #include "chrome/browser/ui/webui/settings/chromeos/search/search_concept.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
+#include "chrome/services/local_search_service/index_impl.h"
 #include "chrome/services/local_search_service/local_search_service_impl.h"
-#include "chrome/services/local_search_service/public/mojom/local_search_service.mojom-test-utils.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
@@ -37,9 +37,8 @@
         profile_manager_.CreateTestingProfile("TestingProfile"),
         &local_search_service_);
 
-    local_search_service_.GetIndex(
-        local_search_service::mojom::LocalSearchService::IndexId::CROS_SETTINGS,
-        index_remote_.BindNewPipeAndPassReceiver());
+    index_ = local_search_service_.GetIndexImpl(
+        local_search_service::IndexId::kCrosSettings);
 
     // Allow asynchronous networking code to complete (networking functionality
     // is tested below).
@@ -49,7 +48,7 @@
   content::BrowserTaskEnvironment task_environment_;
   TestingProfileManager profile_manager_;
   chromeos::network_config::CrosNetworkConfigTestHelper network_config_helper_;
-  mojo::Remote<local_search_service::mojom::Index> index_remote_;
+  local_search_service::IndexImpl* index_;
   local_search_service::LocalSearchServiceImpl local_search_service_;
   std::unique_ptr<OsSettingsLocalizedStringsProvider> provider_;
 };
@@ -58,9 +57,7 @@
 // verifies that when the provider starts up, it adds *some* strings without
 // checking the exact number. It also checks one specific canonical tag.
 TEST_F(OsSettingsLocalizedStringsProviderTest, WifiTags) {
-  uint64_t initial_num_items = 0;
-  local_search_service::mojom::IndexAsyncWaiter(index_remote_.get())
-      .GetSize(&initial_num_items);
+  uint64_t initial_num_items = index_->GetSize();
   EXPECT_GT(initial_num_items, 0u);
 
   const SearchConcept* network_settings_concept =
@@ -82,9 +79,7 @@
       "/device/stub_eth_device", shill::kTypeEthernet, "stub_eth_device");
   base::RunLoop().RunUntilIdle();
 
-  uint64_t num_items_after_adding_ethernet = 0;
-  local_search_service::mojom::IndexAsyncWaiter(index_remote_.get())
-      .GetSize(&num_items_after_adding_ethernet);
+  uint64_t num_items_after_adding_ethernet = index_->GetSize();
   EXPECT_GT(num_items_after_adding_ethernet, initial_num_items);
 
   ethernet_settings_concept =
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 0a2c4de..0f54b68 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -1281,7 +1281,7 @@
       base::ASCIIToUTF16(chrome::kContentSettingsExceptionsLearnMoreURL));
   html_source->AddBoolean(
       "installedAppsInCbd",
-      base::FeatureList::IsEnabled(features::kStoragePressureUI));
+      base::FeatureList::IsEnabled(features::kInstalledAppsInCbd));
   html_source->AddBoolean(
       "driveSuggestAvailable",
       base::FeatureList::IsEnabled(omnibox::kDocumentProvider));
diff --git a/chrome/common/string_matching/fuzzy_tokenized_string_match.cc b/chrome/common/string_matching/fuzzy_tokenized_string_match.cc
index eabc0706..f26a3765 100644
--- a/chrome/common/string_matching/fuzzy_tokenized_string_match.cc
+++ b/chrome/common/string_matching/fuzzy_tokenized_string_match.cc
@@ -319,20 +319,10 @@
                  2;
   } else {
     // Use simple algorithm to calculate match ratio.
-    double partial_match = 0.0;
-    for (const auto& query_token : query.tokens()) {
-      for (const auto& text_token : text.tokens()) {
-        partial_match =
-            std::max(partial_match,
-                     SequenceMatcher(query_token, text_token, use_edit_distance)
-                         .Ratio());
-      }
-    }
-    const double partial_scale = 0.9;
     relevance_ =
-        (std::max(
-             SequenceMatcher(query_text, text_text, use_edit_distance).Ratio(),
-             partial_match * partial_scale) +
+        (SequenceMatcher(base::i18n::ToLower(query_text),
+                         base::i18n::ToLower(text_text), use_edit_distance)
+             .Ratio() +
          prefix_score) /
         2;
   }
diff --git a/chrome/install_static/chromium_install_modes.cc b/chrome/install_static/chromium_install_modes.cc
index ae706e7..c61f84a4 100644
--- a/chrome/install_static/chromium_install_modes.cc
+++ b/chrome/install_static/chromium_install_modes.cc
@@ -20,11 +20,6 @@
 
 const size_t kProductPathNameLength = _countof(kProductPathName) - 1;
 
-// No integration with Google Update, so no app GUID.
-const wchar_t kBinariesAppGuid[] = L"";
-
-const wchar_t kBinariesPathName[] = L"Chromium Binaries";
-
 const char kSafeBrowsingName[] = "chromium";
 
 const InstallConstants kInstallModes[] = {
@@ -63,7 +58,6 @@
         true,   // Supports system-level installs.
         true,   // Supports in-product set as default browser UX.
         false,  // Does not support retention experiments.
-        true,   // Supported multi-install.
         icon_resources::kApplicationIndex,  // App icon resource index.
         IDR_MAINFRAME,                      // App icon resource id.
         L"S-1-15-2-3251537155-1984446955-2931258699-841473695-1938553385-"
diff --git a/chrome/install_static/google_chrome_install_modes.cc b/chrome/install_static/google_chrome_install_modes.cc
index 5b794c8..d858f23 100644
--- a/chrome/install_static/google_chrome_install_modes.cc
+++ b/chrome/install_static/google_chrome_install_modes.cc
@@ -20,11 +20,6 @@
 
 const size_t kProductPathNameLength = _countof(kProductPathName) - 1;
 
-const wchar_t kBinariesAppGuid[] = L"{4DC8B4CA-1BDA-483e-B5FA-D3C12E15B62D}";
-
-// Google Chrome integrates with Google Update, so the app GUID above is used.
-const wchar_t kBinariesPathName[] = L"";
-
 const char kSafeBrowsingName[] = "googlechrome";
 
 const InstallConstants kInstallModes[] = {
@@ -63,7 +58,6 @@
         true,  // Supports system-level installs.
         true,  // Supports in-product set as default browser UX.
         true,  // Supports retention experiments.
-        true,  // Supported multi-install.
         icon_resources::kApplicationIndex,  // App icon resource index.
         IDR_MAINFRAME,                      // App icon resource id.
         L"S-1-15-2-3251537155-1984446955-2931258699-841473695-1938553385-"
@@ -104,7 +98,6 @@
         true,   // Supports system-level installs.
         true,   // Supports in-product set as default browser UX.
         true,   // Supports retention experiments.
-        false,  // Did not support multi-install.
         icon_resources::kBetaApplicationIndex,  // App icon resource index.
         IDR_X005_BETA,                          // App icon resource id.
         L"S-1-15-2-3251537155-1984446955-2931258699-841473695-1938553385-"
@@ -145,7 +138,6 @@
         true,   // Supports system-level installs.
         true,   // Supports in-product set as default browser UX.
         true,   // Supports retention experiments.
-        false,  // Did not support multi-install.
         icon_resources::kDevApplicationIndex,  // App icon resource index.
         IDR_X004_DEV,                          // App icon resource id.
         L"S-1-15-2-3251537155-1984446955-2931258699-841473695-1938553385-"
@@ -186,7 +178,6 @@
         false,  // Does not support system-level installs.
         false,  // Does not support in-product set as default browser UX.
         true,   // Supports retention experiments.
-        false,  // Did not support multi-install.
         icon_resources::kSxSApplicationIndex,  // App icon resource index.
         IDR_SXS,                               // App icon resource id.
         L"S-1-15-2-3251537155-1984446955-2931258699-841473695-1938553385-"
diff --git a/chrome/install_static/install_constants.h b/chrome/install_static/install_constants.h
index 4a17168..7e9680617 100644
--- a/chrome/install_static/install_constants.h
+++ b/chrome/install_static/install_constants.h
@@ -130,9 +130,6 @@
   // following updates.
   bool supports_retention_experiments;
 
-  // True if this mode supported the now-deprecated multi-install.
-  bool supported_multi_install;
-
   // The index of this mode's main application icon in the main executable.
   int app_icon_resource_index;
 
diff --git a/chrome/install_static/install_details.h b/chrome/install_static/install_details.h
index 706caa8..6ddcc4d 100644
--- a/chrome/install_static/install_details.h
+++ b/chrome/install_static/install_details.h
@@ -132,13 +132,6 @@
     return payload_->mode->supports_system_level;
   }
 
-  // True if the mode once supported multi-install, a legacy mode of
-  // installation. This exists to provide migration and cleanup for older
-  // installs.
-  bool supported_multi_install() const {
-    return payload_->mode->supported_multi_install;
-  }
-
   // Returns the resource id of this mode's main application icon.
   int32_t app_icon_resource_id() const {
     return payload_->mode->app_icon_resource_id;
diff --git a/chrome/install_static/install_details_unittest.cc b/chrome/install_static/install_details_unittest.cc
index 099fa38c..56547d1 100644
--- a/chrome/install_static/install_details_unittest.cc
+++ b/chrome/install_static/install_details_unittest.cc
@@ -22,7 +22,6 @@
     constants.size = sizeof(constants);
     constants.install_suffix = L"";
     constants.default_channel_name = L"";
-    constants.supported_multi_install = true;
 #if BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
     constants.app_guid = L"testguid";
     constants.channel_strategy = ChannelStrategy::FIXED;
diff --git a/chrome/install_static/install_modes.cc b/chrome/install_static/install_modes.cc
index 8454b2a..b8195c5e 100644
--- a/chrome/install_static/install_modes.cc
+++ b/chrome/install_static/install_modes.cc
@@ -25,8 +25,8 @@
       .append(app_guid);
 }
 #else
-std::wstring GetUnregisteredKeyPathForProduct(const wchar_t* product) {
-  return std::wstring(L"Software\\").append(product);
+std::wstring GetUnregisteredKeyPathForProduct() {
+  return std::wstring(L"Software\\").append(kProductPathName);
 }
 #endif
 
@@ -34,7 +34,7 @@
 
 std::wstring GetClientsKeyPath(const wchar_t* app_guid) {
 #if !BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
-  return GetUnregisteredKeyPathForProduct(kProductPathName);
+  return GetUnregisteredKeyPathForProduct();
 #else
   return GetClientsKeyPathForApp(app_guid);
 #endif
@@ -42,42 +42,18 @@
 
 std::wstring GetClientStateKeyPath(const wchar_t* app_guid) {
 #if !BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
-  return GetUnregisteredKeyPathForProduct(kProductPathName);
+  return GetUnregisteredKeyPathForProduct();
 #else
   return GetClientStateKeyPathForApp(app_guid);
 #endif
 }
 
-std::wstring GetBinariesClientsKeyPath() {
-#if !BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
-  return GetUnregisteredKeyPathForProduct(kBinariesPathName);
-#else
-  return GetClientsKeyPathForApp(kBinariesAppGuid);
-#endif
-}
-
-std::wstring GetBinariesClientStateKeyPath() {
-#if !BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
-  return GetUnregisteredKeyPathForProduct(kBinariesPathName);
-#else
-  return GetClientStateKeyPathForApp(kBinariesAppGuid);
-#endif
-}
-
 std::wstring GetClientStateMediumKeyPath(const wchar_t* app_guid) {
 #if !BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
-  return GetUnregisteredKeyPathForProduct(kProductPathName);
+  return GetUnregisteredKeyPathForProduct();
 #else
   return GetClientStateMediumKeyPathForApp(app_guid);
 #endif
 }
 
-std::wstring GetBinariesClientStateMediumKeyPath() {
-#if !BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
-  return GetUnregisteredKeyPathForProduct(kBinariesPathName);
-#else
-  return GetClientStateMediumKeyPathForApp(kBinariesAppGuid);
-#endif
-}
-
 }  // namespace install_static
diff --git a/chrome/install_static/install_modes.h b/chrome/install_static/install_modes.h
index 2d6ccbe..0f0eb2a 100644
--- a/chrome/install_static/install_modes.h
+++ b/chrome/install_static/install_modes.h
@@ -56,16 +56,6 @@
 // The length, in characters, of kProductPathName not including the terminator.
 extern const size_t kProductPathNameLength;
 
-// The GUID with which the brand's multi-install binaries were registered with
-// Google Update for modes that once supported the now-deprecated multi-install.
-// Must be empty if the brand does not integrate with Google Update.
-extern const wchar_t kBinariesAppGuid[];
-
-// The name of the registry key in which data for the brand's multi-install
-// binaries were stored for modes that once supported the now-deprecated
-// multi-install. Must be empty if the brand integrates with Google Update.
-extern const wchar_t kBinariesPathName[];
-
 // The brand-specific safe browsing client name.
 extern const char kSafeBrowsingName[];
 
@@ -78,13 +68,10 @@
 // "Software\Chromium Binaries". Otherwise, for brands that do integrate with
 // Google Update, they return something like
 // "Software\Google\Update\ClientState{Medium}\<guid>" where "<guid>" is either
-// |mode|'s appguid or the brand's kBinariesAppGuid.
+// |mode|'s appguid.
 std::wstring GetClientsKeyPath(const wchar_t* app_guid);
 std::wstring GetClientStateKeyPath(const wchar_t* app_guid);
 std::wstring GetClientStateMediumKeyPath(const wchar_t* app_guid);
-std::wstring GetBinariesClientsKeyPath();
-std::wstring GetBinariesClientStateKeyPath();
-std::wstring GetBinariesClientStateMediumKeyPath();
 
 }  // namespace install_static
 
diff --git a/chrome/install_static/install_modes_unittest.cc b/chrome/install_static/install_modes_unittest.cc
index 10ff8f5..5916c99 100644
--- a/chrome/install_static/install_modes_unittest.cc
+++ b/chrome/install_static/install_modes_unittest.cc
@@ -113,18 +113,6 @@
   }
 }
 
-TEST(InstallModes, VerifyBrand) {
-#if BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
-  // Binaries were registered via an app guid with Google Update integration.
-  ASSERT_THAT(kBinariesAppGuid, StrNe(L""));
-  ASSERT_THAT(kBinariesPathName, StrEq(L""));
-#else
-  // Binaries were registered via a different path name without.
-  ASSERT_THAT(kBinariesAppGuid, StrEq(L""));
-  ASSERT_THAT(kBinariesPathName, StrNe(L""));
-#endif
-}
-
 TEST(InstallModes, GetClientsKeyPath) {
   constexpr wchar_t kAppGuid[] = L"test";
 
@@ -149,28 +137,6 @@
 #endif
 }
 
-TEST(InstallModes, GetBinariesClientsKeyPath) {
-#if BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
-  ASSERT_THAT(GetBinariesClientsKeyPath(),
-              StrEq(std::wstring(L"Software\\Google\\Update\\Clients\\")
-                        .append(kBinariesAppGuid)));
-#else
-  ASSERT_THAT(GetBinariesClientsKeyPath(),
-              StrEq(std::wstring(L"Software\\").append(kBinariesPathName)));
-#endif
-}
-
-TEST(InstallModes, GetBinariesClientStateKeyPath) {
-#if BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
-  ASSERT_THAT(GetBinariesClientStateKeyPath(),
-              StrEq(std::wstring(L"Software\\Google\\Update\\ClientState\\")
-                        .append(kBinariesAppGuid)));
-#else
-  ASSERT_THAT(GetBinariesClientStateKeyPath(),
-              StrEq(std::wstring(L"Software\\").append(kBinariesPathName)));
-#endif
-}
-
 TEST(InstallModes, GetClientStateMediumKeyPath) {
   constexpr wchar_t kAppGuid[] = L"test";
 
@@ -183,16 +149,4 @@
 #endif
 }
 
-TEST(InstallModes, GetBinariesClientStateMediumKeyPath) {
-#if BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
-  ASSERT_THAT(
-      GetBinariesClientStateMediumKeyPath(),
-      StrEq(std::wstring(L"Software\\Google\\Update\\ClientStateMedium\\")
-                .append(kBinariesAppGuid)));
-#else
-  ASSERT_THAT(GetBinariesClientStateMediumKeyPath(),
-              StrEq(std::wstring(L"Software\\").append(kBinariesPathName)));
-#endif
-}
-
 }  // namespace install_static
diff --git a/chrome/install_static/install_util.cc b/chrome/install_static/install_util.cc
index 94a97824..e3deeab 100644
--- a/chrome/install_static/install_util.cc
+++ b/chrome/install_static/install_util.cc
@@ -387,14 +387,6 @@
   return GetClientStateMediumKeyPath(GetAppGuid());
 }
 
-std::wstring GetClientStateKeyPathForBinaries() {
-  return GetBinariesClientStateKeyPath();
-}
-
-std::wstring GetClientStateMediumKeyPathForBinaries() {
-  return GetBinariesClientStateMediumKeyPath();
-}
-
 std::wstring GetUninstallRegistryPath() {
   std::wstring result(
       L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\");
@@ -928,16 +920,13 @@
 // InstallDetails instance since it is used to bootstrap InstallDetails.
 std::wstring DetermineChannel(const InstallConstants& mode,
                               bool system_level,
-                              bool from_binaries,
                               std::wstring* update_ap,
                               std::wstring* update_cohort_name) {
 #if !BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
   return std::wstring();
 #else
   // Read the "ap" value and cache it if requested.
-  std::wstring client_state(from_binaries
-                                ? GetBinariesClientStateKeyPath()
-                                : GetClientStateKeyPath(mode.app_guid));
+  std::wstring client_state(GetClientStateKeyPath(mode.app_guid));
   std::wstring ap_value;
   // An empty |ap_value| is used in case of error.
   nt::QueryRegValueSZ(system_level ? nt::HKLM : nt::HKCU, nt::WOW6432,
diff --git a/chrome/install_static/install_util.h b/chrome/install_static/install_util.h
index f3610d31..a2fb9161 100644
--- a/chrome/install_static/install_util.h
+++ b/chrome/install_static/install_util.h
@@ -88,11 +88,6 @@
 // for system-wide installs to hold values written by the browser.
 std::wstring GetClientStateMediumKeyPath();
 
-// Returns the path to the ClientState{,Medium} key for the deprecated Chrome
-// binaries.
-std::wstring GetClientStateKeyPathForBinaries();
-std::wstring GetClientStateMediumKeyPathForBinaries();
-
 // Returns the path
 // "Software\Microsoft\Windows\CurrentVersion\Uninstall\[kCompanyPathName ]
 // kProductPathName[install_suffix]. This is the key used for the browser's
@@ -292,15 +287,12 @@
 bool RecursiveDirectoryCreate(const std::wstring& full_path);
 
 // Returns the unadorned channel name based on the channel strategy for the
-// install mode. |from_binaries| forces the registry locations corresponding to
-// the now-deprecated multi-install binaries to be read, and is only for use by
-// the installer. |update_ap|, if not null, is set to the raw "ap" value read
+// install mode. |update_ap|, if not null, is set to the raw "ap" value read
 // from Chrome's ClientState key in the registry. |update_cohort_name|, if not
 // null, is set to the raw "cohort\name" value read from Chrome's ClientState
 // key in the registry.
 std::wstring DetermineChannel(const InstallConstants& mode,
                               bool system_level,
-                              bool from_binaries,
                               std::wstring* update_ap,
                               std::wstring* update_cohort_name);
 
diff --git a/chrome/install_static/product_install_details.cc b/chrome/install_static/product_install_details.cc
index 2d14cbc1..a68a13a3 100644
--- a/chrome/install_static/product_install_details.cc
+++ b/chrome/install_static/product_install_details.cc
@@ -133,9 +133,8 @@
   // keys and in about:version.
   std::wstring update_ap;
   std::wstring update_cohort_name;
-  details->set_channel(DetermineChannel(*mode, system_level,
-                                        false /* !from_binaries */, &update_ap,
-                                        &update_cohort_name));
+  details->set_channel(
+      DetermineChannel(*mode, system_level, &update_ap, &update_cohort_name));
   details->set_update_ap(update_ap);
   details->set_update_cohort_name(update_cohort_name);
 
diff --git a/chrome/installer/launcher_support/chrome_launcher_support.cc b/chrome/installer/launcher_support/chrome_launcher_support.cc
index b5b10b2..0e5a5e25 100644
--- a/chrome/installer/launcher_support/chrome_launcher_support.cc
+++ b/chrome/installer/launcher_support/chrome_launcher_support.cc
@@ -24,7 +24,6 @@
 const wchar_t kUpdateClientsRegKey[] = L"Software\\Google\\Update\\Clients";
 
 // Copied from google_chrome_install_modes.cc.
-const wchar_t kBinariesAppGuid[] = L"{4DC8B4CA-1BDA-483e-B5FA-D3C12E15B62D}";
 const wchar_t kBrowserAppGuid[] = L"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
 const wchar_t kSxSBrowserAppGuid[] = L"{4ea16ac7-fd5a-47c3-875b-dbf4a2008c20}";
 #else
@@ -96,20 +95,13 @@
 // Returns the path to an existing setup.exe at the specified level, if it can
 // be found via the registry.
 base::FilePath GetSetupExeForInstallationLevel(InstallationLevel level) {
-  base::FilePath setup_exe_path;
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  // Look in the registry for Chrome Binaries first.
-  setup_exe_path = GetSetupExeFromRegistry(level, kBinariesAppGuid);
-  // If the above fails, look in the registry for Chrome next.
-  if (setup_exe_path.empty())
-    setup_exe_path = GetSetupExeFromRegistry(level, kBrowserAppGuid);
-  // If we fail again, then setup_exe_path would be empty.
+  // Look in the registry for Chrome.
+  return GetSetupExeFromRegistry(level, kBrowserAppGuid);
 #else
   // For Chromium, there are no GUIDs. Just look in the Chromium registry key.
-  setup_exe_path = GetSetupExeFromRegistry(level, nullptr);
+  return GetSetupExeFromRegistry(level, nullptr);
 #endif
-
-  return setup_exe_path;
 }
 
 // Returns the path to an installed |exe_file| (e.g. chrome.exe) at the
diff --git a/chrome/installer/launcher_support/chrome_launcher_support.h b/chrome/installer/launcher_support/chrome_launcher_support.h
index df1b17a..c782828 100644
--- a/chrome/installer/launcher_support/chrome_launcher_support.h
+++ b/chrome/installer/launcher_support/chrome_launcher_support.h
@@ -16,9 +16,8 @@
 };
 
 // Returns the path to an installed chrome.exe at the specified level, if it can
-// be found in the registry. Prefers the installer from a multi-install, but may
-// also return that of a single-install of Chrome if no multi-install exists.
-// If |is_sxs| is true, gets the path to the SxS (Canary) version of chrome.exe.
+// be found in the registry. If |is_sxs| is true, gets the path to the SxS
+// (Canary) version of chrome.exe.
 base::FilePath GetChromePathForInstallationLevel(InstallationLevel level,
                                                  bool is_sxs);
 
diff --git a/chrome/installer/mini_installer/configuration.cc b/chrome/installer/mini_installer/configuration.cc
index dfbc86c..aecca377 100644
--- a/chrome/installer/mini_installer/configuration.cc
+++ b/chrome/installer/mini_installer/configuration.cc
@@ -58,7 +58,6 @@
   operation_ = INSTALL_PRODUCT;
   argument_count_ = 0;
   is_system_level_ = false;
-  is_updating_multi_chrome_ = false;
   has_invalid_switch_ = false;
   previous_version_ = NULL;
 }
@@ -92,8 +91,6 @@
   if (!is_system_level_)
     is_system_level_ = GetGoogleUpdateIsMachineEnvVar();
 
-  is_updating_multi_chrome_ = IsUpdatingMultiChrome();
-
   return true;
 }
 
@@ -126,29 +123,4 @@
   previous_version_ = version_string;
 }
 
-bool Configuration::IsUpdatingMultiChrome() const {
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  // Only primary Chrome installs supported multi-install (not canary/SxS).
-  if (chrome_app_guid_ != google_update::kAppGuid)
-    return false;
-
-  // Is Chrome already installed as multi-install?
-  const HKEY root = is_system_level_ ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
-  StackString<128> value;
-  RegKey key;
-  return (OpenClientsKey(root, chrome_app_guid_, KEY_QUERY_VALUE, &key) ==
-              ERROR_SUCCESS &&
-          key.ReadSZValue(kPvRegistryValue, value.get(), value.capacity()) ==
-              ERROR_SUCCESS &&
-          value.length() != 0 &&
-          OpenClientStateKey(root, chrome_app_guid_, KEY_QUERY_VALUE, &key) ==
-              ERROR_SUCCESS &&
-          key.ReadSZValue(kUninstallArgumentsRegistryValue, value.get(),
-                          value.capacity()) == ERROR_SUCCESS &&
-          value.findi(L"--multi-install") != nullptr);
-#else
-  return false;
-#endif
-}
-
 }  // namespace mini_installer
diff --git a/chrome/installer/mini_installer/configuration.h b/chrome/installer/mini_installer/configuration.h
index efb66aa..ed34bf8 100644
--- a/chrome/installer/mini_installer/configuration.h
+++ b/chrome/installer/mini_installer/configuration.h
@@ -46,10 +46,6 @@
   // GoogleUpdateIsMachine=1 is set in the process's environment.
   bool is_system_level() const { return is_system_level_; }
 
-  // Returns true if an existing multi-install Chrome is being updated (Google
-  // Chrome only).
-  bool is_updating_multi_chrome() const { return is_updating_multi_chrome_; }
-
   // Returns true if any invalid switch is found on the command line.
   bool has_invalid_switch() const { return has_invalid_switch_; }
 
@@ -67,16 +63,12 @@
   int argument_count_;
   Operation operation_;
   bool is_system_level_;
-  bool is_updating_multi_chrome_;
   bool has_invalid_switch_;
   const wchar_t* previous_version_;
 
  private:
   Configuration(const Configuration&) = delete;
   Configuration& operator=(const Configuration&) = delete;
-
-  // Returns true if multi-install Chrome is already present on the machine.
-  bool IsUpdatingMultiChrome() const;
 };
 
 }  // namespace mini_installer
diff --git a/chrome/installer/mini_installer/configuration_test.cc b/chrome/installer/mini_installer/configuration_test.cc
index 6b91fb0..d420505a 100644
--- a/chrome/installer/mini_installer/configuration_test.cc
+++ b/chrome/installer/mini_installer/configuration_test.cc
@@ -62,39 +62,6 @@
         registry_overrides_.OverrideRegistry(HKEY_LOCAL_MACHINE));
   }
 
-  // Adds sufficient state in the registry for Configuration to think that
-  // Chrome is already installed at |system_level| as per |multi_install|.
-  void AddChromeRegistryState(bool system_level, bool multi_install) {
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-    static constexpr wchar_t kClientsPath[] =
-        L"SOFTWARE\\Google\\Update\\Clients\\"
-        L"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
-    static constexpr wchar_t kClientStatePath[] =
-        L"SOFTWARE\\Google\\Update\\ClientState\\"
-        L"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
-#else   // BUILDFLAG(GOOGLE_CHROME_BRANDING)
-    static constexpr wchar_t kClientsPath[] = L"SOFTWARE\\Chromium";
-    static constexpr wchar_t kClientStatePath[] = L"SOFTWARE\\Chromium";
-#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
-    base::string16 uninstall_arguments(L"--uninstall");
-    if (system_level)
-      uninstall_arguments += L" --system_level";
-    if (multi_install)
-      uninstall_arguments += L" --multi-install --chrome";
-    const HKEY root = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
-    // Use base::win::RegKey rather than mini_installer's since it's more
-    // prevalent in the codebase and more likely to be easy to understand.
-    base::win::RegKey key;
-    ASSERT_EQ(ERROR_SUCCESS,
-              key.Create(root, kClientsPath, KEY_WOW64_32KEY | KEY_SET_VALUE));
-    ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(L"pv", L"4.3.2.1"));
-    ASSERT_EQ(ERROR_SUCCESS, key.Create(root, kClientStatePath,
-                                        KEY_WOW64_32KEY | KEY_SET_VALUE));
-    ASSERT_EQ(
-        ERROR_SUCCESS,
-        key.WriteValue(L"UninstallArguments", uninstall_arguments.c_str()));
-  }
-
  private:
   registry_util::RegistryOverrideManager registry_overrides_;
 
@@ -144,37 +111,6 @@
   }
 }
 
-TEST_F(MiniInstallerConfigurationTest, IsUpdatingUserSingle) {
-  AddChromeRegistryState(false /* !system_level */, false /* !multi_install */);
-  EXPECT_FALSE(TestConfiguration(L"spam.exe").is_updating_multi_chrome());
-}
-
-TEST_F(MiniInstallerConfigurationTest, IsUpdatingSystemSingle) {
-  AddChromeRegistryState(true /* system_level */, false /* !multi_install */);
-  EXPECT_FALSE(
-      TestConfiguration(L"spam.exe --system-level").is_updating_multi_chrome());
-}
-
-TEST_F(MiniInstallerConfigurationTest, IsUpdatingUserMulti) {
-  AddChromeRegistryState(false /* !system_level */, true /* multi_install */);
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  EXPECT_TRUE(TestConfiguration(L"spam.exe").is_updating_multi_chrome());
-#else
-  EXPECT_FALSE(TestConfiguration(L"spam.exe").is_updating_multi_chrome());
-#endif
-}
-
-TEST_F(MiniInstallerConfigurationTest, IsUpdatingSystemMulti) {
-  AddChromeRegistryState(true /* system_level */, true /* multi_install */);
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  EXPECT_TRUE(
-      TestConfiguration(L"spam.exe --system-level").is_updating_multi_chrome());
-#else
-  EXPECT_FALSE(
-      TestConfiguration(L"spam.exe --system-level").is_updating_multi_chrome());
-#endif
-}
-
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 TEST_F(MiniInstallerConfigurationTest, ChromeAppGuid) {
   EXPECT_STREQ(google_update::kAppGuid,
diff --git a/chrome/installer/mini_installer/mini_installer.cc b/chrome/installer/mini_installer/mini_installer.cc
index 73e6240..7f3bd89 100644
--- a/chrome/installer/mini_installer/mini_installer.cc
+++ b/chrome/installer/mini_installer/mini_installer.cc
@@ -61,22 +61,14 @@
 };
 
 // TODO(grt): Frame this in terms of whether or not the brand supports
-// integation with Omaha, where Google Update is the Google-specific fork of
+// integration with Omaha, where Google Update is the Google-specific fork of
 // the open-source Omaha project.
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-// Opens the Google Update ClientState key. If |binaries| is false, opens the
-// key for Google Chrome or Chrome SxS (canary). If |binaries| is true and an
-// existing multi-install Chrome is being updated, opens the key for the
-// multi-install binaries; otherwise, returns false.
-bool OpenInstallStateKey(const Configuration& configuration,
-                         bool binaries,
-                         RegKey* key) {
-  if (binaries && !configuration.is_updating_multi_chrome())
-    return false;
+// Opens the Google Update ClientState key for the current install mode.
+bool OpenInstallStateKey(const Configuration& configuration, RegKey* key) {
   const HKEY root_key =
       configuration.is_system_level() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
-  const wchar_t* app_guid = binaries ? google_update::kMultiInstallAppGuid
-                                     : configuration.chrome_app_guid();
+  const wchar_t* app_guid = configuration.chrome_app_guid();
   const REGSAM key_access = KEY_QUERY_VALUE | KEY_SET_VALUE;
 
   return OpenClientStateKey(root_key, app_guid, key_access, key) ==
@@ -93,22 +85,18 @@
   if (result.IsSuccess())
     return;
 
-  // Write the value in Chrome ClientState key and in the binaries' if an
-  // existing multi-install Chrome is being updated.
-  for (bool binaries : {false, true}) {
-    RegKey key;
-    DWORD value;
-    if (OpenInstallStateKey(configuration, binaries, &key)) {
-      if (key.ReadDWValue(kInstallerResultRegistryValue, &value) !=
-              ERROR_SUCCESS ||
-          value == 0) {
-        key.WriteDWValue(kInstallerResultRegistryValue,
-                         result.exit_code ? 1 /* FAILED_CUSTOM_ERROR */
-                                          : 0 /* SUCCESS */);
-        key.WriteDWValue(kInstallerErrorRegistryValue, result.exit_code);
-        key.WriteDWValue(kInstallerExtraCode1RegistryValue,
-                         result.windows_error);
-      }
+  // Write the value in Chrome ClientState key.
+  RegKey key;
+  DWORD value;
+  if (OpenInstallStateKey(configuration, &key)) {
+    if (key.ReadDWValue(kInstallerResultRegistryValue, &value) !=
+            ERROR_SUCCESS ||
+        value == 0) {
+      key.WriteDWValue(kInstallerResultRegistryValue,
+                       result.exit_code ? 1 /* FAILED_CUSTOM_ERROR */
+                                        : 0 /* SUCCESS */);
+      key.WriteDWValue(kInstallerErrorRegistryValue, result.exit_code);
+      key.WriteDWValue(kInstallerExtraCode1RegistryValue, result.windows_error);
     }
   }
 }
@@ -119,26 +107,27 @@
 void SetInstallerFlags(const Configuration& configuration) {
   StackString<128> value;
 
-  for (bool binaries : {false, true}) {
-    RegKey key;
-    if (!OpenInstallStateKey(configuration, binaries, &key))
-      continue;
+  RegKey key;
+  if (!OpenInstallStateKey(configuration, &key))
+    return;
 
-    LONG ret = key.ReadSZValue(kApRegistryValue, value.get(), value.capacity());
+  // TODO(grt): Trim legacy modifiers (chrome,chromeframe,apphost,applauncher,
+  // multi,readymode,stage,migrating,multifail) from the ap value.
 
-    // The conditions below are handling two cases:
-    // 1. When ap value is present, we want to add the required tag only if it
-    //    is not present.
-    // 2. When ap value is missing, we are going to create it with the required
-    //    tag.
-    if ((ret == ERROR_SUCCESS) || (ret == ERROR_FILE_NOT_FOUND)) {
-      if (ret == ERROR_FILE_NOT_FOUND)
-        value.clear();
+  LONG ret = key.ReadSZValue(kApRegistryValue, value.get(), value.capacity());
 
-      if (!StrEndsWith(value.get(), kFullInstallerSuffix) &&
-          value.append(kFullInstallerSuffix)) {
-        key.WriteSZValue(kApRegistryValue, value.get());
-      }
+  // The conditions below are handling two cases:
+  // 1. When ap value is present, we want to add the required tag only if it
+  //    is not present.
+  // 2. When ap value is missing, we are going to create it with the required
+  //    tag.
+  if ((ret == ERROR_SUCCESS) || (ret == ERROR_FILE_NOT_FOUND)) {
+    if (ret == ERROR_FILE_NOT_FOUND)
+      value.clear();
+
+    if (!StrEndsWith(value.get(), kFullInstallerSuffix) &&
+        value.append(kFullInstallerSuffix)) {
+      key.WriteSZValue(kApRegistryValue, value.get());
     }
   }
 }
@@ -184,24 +173,11 @@
 ProcessExitResult GetPreviousSetupExePath(const Configuration& configuration,
                                           wchar_t* path,
                                           size_t size) {
-  bool system_level = configuration.is_system_level();
-  const wchar_t* previous_version = configuration.previous_version();
-  ProcessExitResult exit_code = ProcessExitResult(GENERIC_ERROR);
-
   // Check Chrome's ClientState key for the path to setup.exe. This will have
   // the correct path for all well-functioning installs.
-  exit_code =
-      GetSetupExePathForAppGuid(system_level, configuration.chrome_app_guid(),
-                                previous_version, path, size);
-
-  // Failing that, check the binaries if updating multi-install Chrome.
-  if (!exit_code.IsSuccess() && configuration.is_updating_multi_chrome()) {
-    exit_code = GetSetupExePathForAppGuid(system_level,
-                                          google_update::kMultiInstallAppGuid,
-                                          previous_version, path, size);
-  }
-
-  return exit_code;
+  return GetSetupExePathForAppGuid(
+      configuration.is_system_level(), configuration.chrome_app_guid(),
+      configuration.previous_version(), path, size);
 }
 
 // Calls CreateProcess with good default parameters and waits for the process to
diff --git a/chrome/installer/mini_installer/mini_installer_constants.cc b/chrome/installer/mini_installer/mini_installer_constants.cc
index fa9a348b..03b5ac0d 100644
--- a/chrome/installer/mini_installer/mini_installer_constants.cc
+++ b/chrome/installer/mini_installer/mini_installer_constants.cc
@@ -31,8 +31,6 @@
 // ap value suffix to force subsequent updates to use the full rather than
 // differential updater.
 const wchar_t kFullInstallerSuffix[] = L"-full";
-// ap value tag for a multi-install product.
-const wchar_t kMultiInstallTag[] = L"-multi";
 
 // The resource types that would be unpacked from the mini installer.
 // Uncompressed binary.
diff --git a/chrome/installer/mini_installer/mini_installer_constants.h b/chrome/installer/mini_installer/mini_installer_constants.h
index 170e9ae2..71d57066 100644
--- a/chrome/installer/mini_installer/mini_installer_constants.h
+++ b/chrome/installer/mini_installer/mini_installer_constants.h
@@ -24,7 +24,6 @@
 
 extern const wchar_t kTempPrefix[];
 extern const wchar_t kFullInstallerSuffix[];
-extern const wchar_t kMultiInstallTag[];
 
 // The resource types that would be unpacked from the mini installer.
 extern const wchar_t kBinResourceType[];
diff --git a/chrome/installer/setup/google_chrome_behaviors.cc b/chrome/installer/setup/google_chrome_behaviors.cc
index af915b06..0a153c4d 100644
--- a/chrome/installer/setup/google_chrome_behaviors.cc
+++ b/chrome/installer/setup/google_chrome_behaviors.cc
@@ -86,7 +86,7 @@
 // its ClientState key if it is not present, resulting in the full installer
 // being returned from the next update check. If |archive_type| is
 // FULL_ARCHIVE_TYPE or |install_status| indicates a successful update, "-full"
-// is removed from the "ap" value. "-multifail" and "-stage:*" values are
+// is removed from the "ap" value. "-stage:*" values are
 // unconditionally removed from the "ap" value.
 void UpdateInstallStatus(installer::ArchiveType archive_type,
                          installer::InstallStatus install_status) {
diff --git a/chrome/installer/setup/install.cc b/chrome/installer/setup/install.cc
index 79b29d5..0ace0dc9 100644
--- a/chrome/installer/setup/install.cc
+++ b/chrome/installer/setup/install.cc
@@ -488,13 +488,6 @@
   // TODO(robertshield): Everything below this line should instead be captured
   // by WorkItems.
   if (!InstallUtil::GetInstallReturnCode(result)) {
-    installer_state.SetStage(UPDATING_CHANNELS);
-
-    // Strip evidence of multi-install from the "ap" value.
-    // TODO(grt): Consider doing this earlier, prior to any other work, so that
-    // failed updates benefit from the stripping.
-    installer_state.UpdateChannels();
-
     installer_state.SetStage(COPYING_PREFERENCES_FILE);
 
     if (result == FIRST_INSTALL_SUCCESS && !prefs_path.empty())
diff --git a/chrome/installer/setup/install_worker.cc b/chrome/installer/setup/install_worker.cc
index c324c302..fe48d1e 100644
--- a/chrome/installer/setup/install_worker.cc
+++ b/chrome/installer/setup/install_worker.cc
@@ -383,52 +383,6 @@
   return AddAclToPath(path, converted_trustees, access_mask, ace_flags);
 }
 
-// Migrates consent for the collection of usage statistics from the binaries to
-// Chrome when migrating multi-install Chrome to single-install.
-void AddMigrateUsageStatsWorkItems(const InstallerState& installer_state,
-                                   WorkItemList* install_list) {
-  // This operation only applies to modes that once supported multi-install.
-  if (install_static::InstallDetails::Get().supported_multi_install())
-    return;
-
-  // Bail out if an existing multi-install Chrome is not being migrated to
-  // single-install.
-  if (!installer_state.is_migrating_to_single())
-    return;
-
-  // Nothing to do if the binaries aren't actually installed.
-  if (!AreBinariesInstalled(installer_state))
-    return;
-
-  // Delete any stale value in Chrome's ClientStateMedium key. A new value, if
-  // found, will be written to the ClientState key below.
-  if (installer_state.system_install()) {
-    install_list->AddDeleteRegValueWorkItem(
-        installer_state.root_key(),
-        install_static::GetClientStateMediumKeyPath(), KEY_WOW64_64KEY,
-        google_update::kRegUsageStatsField);
-  }
-
-  google_update::Tristate consent =
-      GoogleUpdateSettings::GetCollectStatsConsentForBinaries();
-  if (consent == google_update::TRISTATE_NONE) {
-    VLOG(1) << "No consent value found to migrate to single-install.";
-    // Delete any stale value in Chrome's ClientState key.
-    install_list->AddDeleteRegValueWorkItem(
-        installer_state.root_key(), install_static::GetClientStateKeyPath(),
-        KEY_WOW64_64KEY, google_update::kRegUsageStatsField);
-    return;
-  }
-
-  VLOG(1) << "Migrating usage stats consent from multi- to single-install.";
-
-  // Write consent to Chrome's ClientState key.
-  install_list->AddSetRegValueWorkItem(
-      installer_state.root_key(), install_static::GetClientStateKeyPath(),
-      KEY_WOW64_32KEY, google_update::kRegUsageStatsField,
-      static_cast<DWORD>(consent), true);
-}
-
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 // Adds work items to register the Elevation Service with Windows. Only for
 // system level installs.
@@ -996,9 +950,6 @@
   InstallUtil::AddUpdateDowngradeVersionItem(
       installer_state.root_key(), current_version, new_version, install_list);
 
-  // Migrate usagestats back to Chrome.
-  AddMigrateUsageStatsWorkItems(installer_state, install_list);
-
   AddUpdateBrandCodeWorkItem(installer_state, install_list);
 
   // Append the tasks that run after the installation.
diff --git a/chrome/installer/setup/installer_state.cc b/chrome/installer/setup/installer_state.cc
index 77f49b0..f0b7fba 100644
--- a/chrome/installer/setup/installer_state.cc
+++ b/chrome/installer/setup/installer_state.cc
@@ -43,16 +43,14 @@
       level_(UNKNOWN_LEVEL),
       root_key_(NULL),
       msi_(false),
-      verbose_logging_(false),
-      is_migrating_to_single_(false) {}
+      verbose_logging_(false) {}
 
 InstallerState::InstallerState(Level level)
     : operation_(UNINITIALIZED),
       level_(UNKNOWN_LEVEL),
       root_key_(NULL),
       msi_(false),
-      verbose_logging_(false),
-      is_migrating_to_single_(false) {
+      verbose_logging_(false) {
   // Use set_level() so that root_key_ is updated properly.
   set_level(level);
 }
@@ -89,14 +87,7 @@
 
   VLOG(1) << (is_uninstall ? "Uninstall Chrome" : "Install Chrome");
 
-  if (is_uninstall) {
-    operation_ = UNINSTALL;
-  } else {
-    operation_ = SINGLE_INSTALL_OR_UPDATE;
-    // Is this a migration from multi-install to single-install?
-    const ProductState* state = machine_state.GetProductState(system_install());
-    is_migrating_to_single_ = state && state->is_multi_install();
-  }
+  operation_ = is_uninstall ? UNINSTALL : SINGLE_INSTALL_OR_UPDATE;
 
   // Parse --critical-update-version=W.X.Y.Z
   std::string critical_version_value(
@@ -177,7 +168,6 @@
   root_key_ = NULL;
   msi_ = false;
   verbose_logging_ = false;
-  is_migrating_to_single_ = false;
 }
 
 void InstallerState::SetStage(InstallerStage stage) const {
@@ -185,40 +175,6 @@
                                     progress_calculator_.Calculate(stage));
 }
 
-void InstallerState::UpdateChannels() const {
-  DCHECK_NE(UNINSTALL, operation_);
-  // Update the "ap" value for the product being installed/updated.  Use the
-  // current value in the registry since the InstallationState instance used by
-  // the bulk of the installer does not track changes made by UpdateStage.
-  // Create the app's ClientState key if it doesn't exist.
-  ChannelInfo channel_info;
-  base::win::RegKey state_key;
-  LONG result =
-      state_key.Create(root_key_,
-                       state_key_.c_str(),
-                       KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_WOW64_32KEY);
-  if (result == ERROR_SUCCESS) {
-    channel_info.Initialize(state_key);
-
-    // Multi-install has been deprecated. All installs and updates are single.
-    bool modified = channel_info.SetMultiInstall(false);
-
-    // Remove all multi-install products from the channel name.
-    modified |= channel_info.SetChrome(false);
-    modified |= channel_info.SetChromeFrame(false);
-    modified |= channel_info.SetAppLauncher(false);
-
-    VLOG(1) << "ap: " << channel_info.value();
-
-    // Write the results if needed.
-    if (modified)
-      channel_info.Write(&state_key);
-  } else {
-    LOG(ERROR) << "Failed opening key " << state_key_
-               << " to update app channels; result: " << result;
-  }
-}
-
 void InstallerState::WriteInstallerResult(
     InstallStatus status,
     int string_resource_id,
@@ -233,22 +189,6 @@
   InstallUtil::AddInstallerResultItems(
       system_install, install_static::GetClientStateKeyPath(), status,
       string_resource_id, launch_cmd, install_list.get());
-  if (is_migrating_to_single() && InstallUtil::GetInstallReturnCode(status)) {
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-    // Write to the binaries on error if this is a migration back to
-    // single-install for Google Chrome builds. Skip this for Chromium builds
-    // because they lump the "ClientState" and "Clients" keys into a single
-    // key. As a consequence, writing this value causes Software\Chromium to be
-    // re-created after it was deleted during the migration to single-install.
-    // Google Chrome builds don't suffer this since the two keys are distinct
-    // and have different lifetimes. The result is only written on failure since
-    // for success, the binaries have been uninstalled and therefore the result
-    // will not be read by Google Update.
-    InstallUtil::AddInstallerResultItems(
-        system_install, install_static::GetClientStateKeyPathForBinaries(),
-        status, string_resource_id, launch_cmd, install_list.get());
-#endif
-  }
   install_list->Do();
 }
 
diff --git a/chrome/installer/setup/installer_state.h b/chrome/installer/setup/installer_state.h
index e30f0ac..e8bdbda 100644
--- a/chrome/installer/setup/installer_state.h
+++ b/chrome/installer/setup/installer_state.h
@@ -86,10 +86,6 @@
   // The ClientState key by which we interact with Google Update.
   const base::string16& state_key() const { return state_key_; }
 
-  // Returns true if this is an update of multi-install Chrome to
-  // single-install.
-  bool is_migrating_to_single() const { return is_migrating_to_single_; }
-
   // Returns the currently installed version in |target_path|, or NULL if no
   // products are installed. Ownership is passed to the caller.
   base::Version* GetCurrentVersion(
@@ -112,9 +108,6 @@
   // Google Update for presentation to a user.
   void SetStage(InstallerStage stage) const;
 
-  // Strips all evidence of multi-install from Chrome's "ap" value.
-  void UpdateChannels() const;
-
   // Sets installer result information in the registry for consumption by Google
   // Update. The InstallerResult value is set to 0 (SUCCESS) or 1
   // (FAILED_CUSTOM_ERROR) depending on whether |status| maps to success or not.
@@ -145,7 +138,6 @@
   HKEY root_key_;
   bool msi_;
   bool verbose_logging_;
-  bool is_migrating_to_single_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(InstallerState);
diff --git a/chrome/installer/setup/installer_state_unittest.cc b/chrome/installer/setup/installer_state_unittest.cc
index dc0e2a34..33abdc8 100644
--- a/chrome/installer/setup/installer_state_unittest.cc
+++ b/chrome/installer/setup/installer_state_unittest.cc
@@ -116,39 +116,34 @@
   std::wstring value;
   DWORD dw_value;
 
-  // Check results for a fresh install of Chrome and the same for an attempt at
-  // multi-install, which is now ignored.
-  static constexpr const wchar_t* kCommandLines[] = {
-      L"setup.exe --system-level",
-      L"setup.exe --system-level --multi-install --chrome",
-  };
-  for (const wchar_t* command_line : kCommandLines) {
-    RegistryOverrideManager override_manager;
-    ASSERT_NO_FATAL_FAILURE(override_manager.OverrideRegistry(root));
-    base::CommandLine cmd_line = base::CommandLine::FromString(command_line);
-    const MasterPreferences prefs(cmd_line);
-    InstallationState machine_state;
-    machine_state.Initialize();
-    InstallerState state;
-    state.Initialize(cmd_line, prefs, machine_state);
-    state.WriteInstallerResult(installer::FIRST_INSTALL_SUCCESS,
-                               IDS_INSTALL_OS_ERROR_BASE, &launch_cmd);
-    EXPECT_EQ(ERROR_SUCCESS,
-              key.Open(root, install_static::GetClientStateKeyPath().c_str(),
-                       KEY_READ));
-    EXPECT_EQ(ERROR_SUCCESS,
-        key.ReadValueDW(installer::kInstallerResult, &dw_value));
-    EXPECT_EQ(static_cast<DWORD>(0), dw_value);
-    EXPECT_EQ(ERROR_SUCCESS,
-        key.ReadValueDW(installer::kInstallerError, &dw_value));
-    EXPECT_EQ(static_cast<DWORD>(installer::FIRST_INSTALL_SUCCESS), dw_value);
-    EXPECT_EQ(ERROR_SUCCESS,
-        key.ReadValue(installer::kInstallerResultUIString, &value));
-    EXPECT_FALSE(value.empty());
-    EXPECT_EQ(ERROR_SUCCESS,
-        key.ReadValue(installer::kInstallerSuccessLaunchCmdLine, &value));
-    EXPECT_EQ(launch_cmd, value);
-  }
+  // Check results for a fresh install of Chrome.
+  constexpr wchar_t command_line[] = L"setup.exe --system-level";
+
+  RegistryOverrideManager override_manager;
+  ASSERT_NO_FATAL_FAILURE(override_manager.OverrideRegistry(root));
+  base::CommandLine cmd_line = base::CommandLine::FromString(command_line);
+  const MasterPreferences prefs(cmd_line);
+  InstallationState machine_state;
+  machine_state.Initialize();
+  InstallerState state;
+  state.Initialize(cmd_line, prefs, machine_state);
+  state.WriteInstallerResult(installer::FIRST_INSTALL_SUCCESS,
+                             IDS_INSTALL_OS_ERROR_BASE, &launch_cmd);
+  EXPECT_EQ(ERROR_SUCCESS,
+            key.Open(root, install_static::GetClientStateKeyPath().c_str(),
+                     KEY_READ));
+  EXPECT_EQ(ERROR_SUCCESS,
+            key.ReadValueDW(installer::kInstallerResult, &dw_value));
+  EXPECT_EQ(static_cast<DWORD>(0), dw_value);
+  EXPECT_EQ(ERROR_SUCCESS,
+            key.ReadValueDW(installer::kInstallerError, &dw_value));
+  EXPECT_EQ(static_cast<DWORD>(installer::FIRST_INSTALL_SUCCESS), dw_value);
+  EXPECT_EQ(ERROR_SUCCESS,
+            key.ReadValue(installer::kInstallerResultUIString, &value));
+  EXPECT_FALSE(value.empty());
+  EXPECT_EQ(ERROR_SUCCESS,
+            key.ReadValue(installer::kInstallerSuccessLaunchCmdLine, &value));
+  EXPECT_EQ(launch_cmd, value);
 }
 
 TEST_F(InstallerStateTest, InitializeTwice) {
diff --git a/chrome/installer/setup/setup_install_details.cc b/chrome/installer/setup/setup_install_details.cc
index 92f10c12..40f2be9 100644
--- a/chrome/installer/setup/setup_install_details.cc
+++ b/chrome/installer/setup/setup_install_details.cc
@@ -31,27 +31,6 @@
   return &install_static::kInstallModes[0];
 }
 
-// Returns true if Chrome is installed (i.e., has a "pv" value) and its
-// UninstallArguments contains "--multi-install".
-bool IsUpdatingFromMulti(const install_static::InstallConstants& mode,
-                         bool system_level) {
-  const HKEY root = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
-  const wchar_t* const app_guid = mode.app_guid;
-  base::win::RegKey key;
-  base::string16 value;
-
-  return key.Open(root, install_static::GetClientsKeyPath(app_guid).c_str(),
-                  KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS &&
-         key.ReadValue(google_update::kRegVersionField, &value) ==
-             ERROR_SUCCESS &&
-         !value.empty() &&
-         key.Open(root, install_static::GetClientStateKeyPath(app_guid).c_str(),
-                  KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS &&
-         key.ReadValue(installer::kUninstallArgumentsField, &value) ==
-             ERROR_SUCCESS &&
-         value.find(L"--multi-install") != base::string16::npos;
-}
-
 }  // namespace
 
 void InitializeInstallDetails(
@@ -87,18 +66,15 @@
   // ChannelStrategy. For brands that do not support Google Update, the channel
   // is an empty string. For modes using the FIXED strategy, the channel is the
   // default_channel_name in the mode. For modes using the ADDITIONAL_PARAMETERS
-  // strategy, the channel is parsed from the "ap" value in either the binaries'
-  // ClientState registry key or the mode's ClientState registry key. Which one
-  // is used depends on whether or not this Chrome is updating from a legacy
-  // multi-install Chrome.
+  // strategy, the channel is parsed from the "ap" value in the mode's
+  // ClientState registry key.
 
   // Cache the ap and cohort name values found in the registry for use in crash
   // keys.
   base::string16 update_ap;
   base::string16 update_cohort_name;
   details->set_channel(install_static::DetermineChannel(
-      *mode, system_level, IsUpdatingFromMulti(*mode, system_level), &update_ap,
-      &update_cohort_name));
+      *mode, system_level, &update_ap, &update_cohort_name));
   details->set_update_ap(update_ap);
   details->set_update_cohort_name(update_cohort_name);
 
diff --git a/chrome/installer/setup/setup_install_details_unittest.cc b/chrome/installer/setup/setup_install_details_unittest.cc
index 656f5e64..e0ba8ad 100644
--- a/chrome/installer/setup/setup_install_details_unittest.cc
+++ b/chrome/installer/setup/setup_install_details_unittest.cc
@@ -24,7 +24,6 @@
   const wchar_t* command_line;
   const wchar_t* uninstall_args;
   const wchar_t* product_ap;
-  const wchar_t* binaries_ap;
 
   // Expectations:
   install_static::InstallConstantIndex index;
@@ -39,7 +38,6 @@
         L"setup.exe",                  // User-level, primary mode.
         L"",                           // New install.
         L"x64-stable",                 // Stable channel.
-        L"1.1-beta",                   // Value ignored in binaries.
         install_static::STABLE_INDEX,  // Expect primary mode.
         false,                         // Expect user-level.
         L"",                           // Expect stable channel.
@@ -48,25 +46,14 @@
         L"setup.exe",                  // User-level, primary mode.
         L"--uninstall",                // Updating an existing install.
         L"x64-stable",                 // Stable channel.
-        L"1.1-beta",                   // Value ignored in binaries.
         install_static::STABLE_INDEX,  // Expect primary mode.
         false,                         // Expect user-level.
         L"",                           // Expect stable channel.
     },
     {
-        L"setup.exe",                    // User-level, primary mode.
-        L"--uninstall --multi-install",  // Updating an existing multi-install.
-        L"x64-stable",                   // Value ignored in product.
-        L"1.1-beta",                     // Channel read from binaries.
-        install_static::STABLE_INDEX,    // Expect primary mode.
-        false,                           // Expect user-level.
-        L"beta",                         // Expect beta channel.
-    },
-    {
         L"setup.exe",                  // User-level, primary mode.
         L"",                           // New install.
         L"1.1-beta",                   // Beta channel.
-        L"x64-stable",                 // Value ignored in binaries.
         install_static::STABLE_INDEX,  // Expect primary mode.
         false,                         // Expect user-level.
         L"beta",                       // Expect beta channel.
@@ -75,7 +62,6 @@
         L"setup.exe --chrome-beta",  // User-level, secondary SxS beta mode.
         L"",                         // New install.
         L"",                         // Unused.
-        L"",                         // Unused.
         install_static::BETA_INDEX,  // Expect SxS beta mode.
         false,                       // Expect user-level.
         L"beta",                     // Expect beta channel.
@@ -84,7 +70,6 @@
         L"setup.exe --chrome-beta",    // User-level, secondary SxS beta mode.
         L"--uninstall --chrome-beta",  // Update.
         L"",                           // Unused.
-        L"",                           // Unused.
         install_static::BETA_INDEX,    // Expect SxS beta mode.
         false,                         // Expect user-level.
         L"beta",                       // Expect beta channel.
@@ -93,7 +78,6 @@
         L"setup.exe --chrome-dev",  // User-level, secondary SxS dev mode.
         L"",                        // New install.
         L"",                        // Unused.
-        L"",                        // Unused.
         install_static::DEV_INDEX,  // Expect SxS dev mode.
         false,                      // Expect user-level.
         L"dev",                     // Expect dev channel.
@@ -102,7 +86,6 @@
         L"setup.exe --chrome-dev",    // User-level, secondary SxS dev mode.
         L"--uninstall --chrome-dev",  // Update.
         L"",                          // Unused.
-        L"",                          // Unused.
         install_static::DEV_INDEX,    // Expect SxS dev mode.
         false,                        // Expect user-level.
         L"dev",                       // Expect dev channel.
@@ -111,7 +94,6 @@
         L"setup.exe --chrome-sxs",     // User-level, secondary SxS canary mode.
         L"",                           // New install.
         L"",                           // Unused.
-        L"",                           // Unused.
         install_static::CANARY_INDEX,  // Expect SxS canary mode.
         false,                         // Expect user-level.
         L"canary",                     // Expect canary channel.
@@ -120,7 +102,6 @@
         L"setup.exe --chrome-sxs",     // User-level, secondary SxS canary mode.
         L"--uninstall --chrome-sxs",   // Update.
         L"",                           // Unused.
-        L"",                           // Unused.
         install_static::CANARY_INDEX,  // Expect SxS canary mode.
         false,                         // Expect user-level.
         L"canary",                     // Expect canary channel.
@@ -131,7 +112,6 @@
         L"setup.exe --system-level",   // System-level, primary mode.
         L"",                           // New install.
         L"x64-stable",                 // Stable channel.
-        L"1.1-beta",                   // Value ignored in binaries.
         install_static::STABLE_INDEX,  // Expect primary mode.
         true,                          // Expect system-level.
         L"",                           // Expect stable channel.
@@ -140,26 +120,14 @@
         L"setup.exe --system-level",    // System-level, primary mode.
         L"--uninstall --system-level",  // Updating an existing install.
         L"x64-stable",                  // Stable channel.
-        L"1.1-beta",                    // Value ignored in binaries.
         install_static::STABLE_INDEX,   // Expect primary mode.
         true,                           // Expect system-level.
         L"",                            // Expect stable channel.
     },
     {
-        L"setup.exe --system-level",  // System-level, primary mode.
-        // Updating an existing multi-install.
-        L"--uninstall --system-level --multi-install",
-        L"x64-stable",                 // Value ignored in product.
-        L"1.1-beta",                   // Channel read from binaries.
-        install_static::STABLE_INDEX,  // Expect primary mode.
-        true,                          // Expect system-level.
-        L"beta",                       // Expect beta channel.
-    },
-    {
         L"setup.exe --system-level",   // System-level, primary mode.
         L"",                           // New install.
         L"1.1-beta",                   // Beta channel.
-        L"x64-stable",                 // Value ignored in binaries.
         install_static::STABLE_INDEX,  // Expect primary mode.
         true,                          // Expect system-level.
         L"beta",                       // Expect beta channel.
@@ -169,7 +137,6 @@
                                                     // beta mode.
         L"",                                        // New install.
         L"",                                        // Unused.
-        L"",                                        // Unused.
         install_static::BETA_INDEX,                 // Expect SxS beta mode.
         true,                                       // Expect user-level.
         L"beta",                                    // Expect beta channel.
@@ -179,7 +146,6 @@
                                                     // beta mode.
         L"--uninstall --system-level --chrome-beta",  // Update.
         L"",                                          // Unused.
-        L"",                                          // Unused.
         install_static::BETA_INDEX,                   // Expect SxS beta mode.
         true,                                         // Expect user-level.
         L"beta",                                      // Expect beta channel.
@@ -189,7 +155,6 @@
                                                    // dev mode.
         L"",                                       // New install.
         L"",                                       // Unused.
-        L"",                                       // Unused.
         install_static::DEV_INDEX,                 // Expect SxS dev mode.
         true,                                      // Expect user-level.
         L"dev",                                    // Expect dev channel.
@@ -199,7 +164,6 @@
                                                    // dev mode.
         L"--uninstall --system-level --chrome-dev",  // Update.
         L"",                                         // Unused.
-        L"",                                         // Unused.
         install_static::DEV_INDEX,                   // Expect SxS dev mode.
         true,                                        // Expect user-level.
         L"dev",                                      // Expect dev channel.
@@ -212,7 +176,6 @@
         L"setup.exe",                    // User-level, primary mode.
         L"",                             // New install.
         L"",                             // Channels are not supported.
-        L"",                             // Channels are not supported.
         install_static::CHROMIUM_INDEX,  // Expect primary mode.
         false,                           // Expect user-level.
         L"",                             // Expect empty channel.
@@ -221,7 +184,6 @@
         L"setup.exe",                    // User-level, primary mode.
         L"--uninstall",                  // Updating an existing install.
         L"",                             // Channels are not supported.
-        L"",                             // Channels are not supported.
         install_static::CHROMIUM_INDEX,  // Expect primary mode.
         false,                           // Expect user-level.
         L"",                             // Expect empty channel.
@@ -232,7 +194,6 @@
         L"setup.exe --system-level",     // System-level, primary mode.
         L"",                             // New install.
         L"",                             // Channels are not supported.
-        L"",                             // Channels are not supported.
         install_static::CHROMIUM_INDEX,  // Expect primary mode.
         true,                            // Expect system-level.
         L"",                             // Expect empty channel.
@@ -241,7 +202,6 @@
         L"setup.exe --system-level",     // System-level, primary mode.
         L"--uninstall --system-level",   // Updating an existing install.
         L"",                             // Channels are not supported.
-        L"",                             // Channels are not supported.
         install_static::CHROMIUM_INDEX,  // Expect primary mode.
         true,                            // Expect system-level.
         L"",                             // Expect empty channel.
@@ -277,7 +237,6 @@
     ASSERT_NO_FATAL_FAILURE(SetProductAp(
         root_key_, install_static::kInstallModes[test_data_.index].app_guid,
         test_data_.product_ap));
-    ASSERT_NO_FATAL_FAILURE(SetBinariesAp(root_key_, test_data_.binaries_ap));
 #endif
   }
 
@@ -330,18 +289,6 @@
         Eq(ERROR_SUCCESS));
   }
 
-  static void SetBinariesAp(HKEY root_key, const wchar_t* ap) {
-    // Do nothing if there's no value to write.
-    if (!ap || !*ap)
-      return;
-    ASSERT_THAT(
-        base::win::RegKey(
-            root_key, install_static::GetBinariesClientStateKeyPath().c_str(),
-            KEY_WOW64_32KEY | KEY_SET_VALUE)
-            .WriteValue(L"ap", ap),
-        Eq(ERROR_SUCCESS));
-  }
-
   registry_util::RegistryOverrideManager override_manager_;
   const TestData& test_data_;
   HKEY root_key_;
diff --git a/chrome/installer/setup/setup_main.cc b/chrome/installer/setup/setup_main.cc
index 8977ca8..7d91bfd 100644
--- a/chrome/installer/setup/setup_main.cc
+++ b/chrome/installer/setup/setup_main.cc
@@ -659,8 +659,7 @@
   installer::InstallStatus install_status = installer::UNKNOWN_STATUS;
   installer::ArchiveType archive_type = installer::UNKNOWN_ARCHIVE_TYPE;
   installer_state->SetStage(installer::PRECONDITIONS);
-  // Remove any legacy "-multifail" or "-stage:*" values from the product's
-  // "ap" value.
+  // Remove any legacy "-stage:*" values from the product's "ap" value.
   installer::UpdateInstallStatus(archive_type, install_status);
 
   // Drop to background processing mode if the process was started below the
@@ -1332,9 +1331,6 @@
   InstallerState installer_state;
   installer_state.Initialize(cmd_line, prefs, original_state);
 
-  VLOG(1) << "is_migrating_to_single is "
-          << installer_state.is_migrating_to_single();
-
   persistent_histogram_storage.set_storage_base_dir(
       installer_state.target_path());
 
diff --git a/chrome/installer/setup/setup_util.cc b/chrome/installer/setup/setup_util.cc
index c9f353a..21cf6de 100644
--- a/chrome/installer/setup/setup_util.cc
+++ b/chrome/installer/setup/setup_util.cc
@@ -139,7 +139,13 @@
 // for this mode of install was dropped from ToT in December 2016. Remove any
 // stray bits in the registry leftover from such installs.
 void RemoveBinariesVersionKey(const InstallerState& installer_state) {
-  base::string16 path(install_static::GetBinariesClientsKeyPath());
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  base::string16 path(install_static::GetClientsKeyPath(
+      L"{4DC8B4CA-1BDA-483e-B5FA-D3C12E15B62D}"));
+#else
+  // Assume that non-Google is Chromium branding.
+  base::string16 path(L"Software\\Chromium Binaries");
+#endif
   if (base::win::RegKey(installer_state.root_key(), path.c_str(),
                         KEY_QUERY_VALUE | KEY_WOW64_32KEY)
           .Valid()) {
@@ -679,21 +685,6 @@
                                  WorkItem::kWow64Default);
 }
 
-bool AreBinariesInstalled(const InstallerState& installer_state) {
-  if (!install_static::InstallDetails::Get().supported_multi_install())
-    return false;
-
-  base::win::RegKey key;
-  base::string16 pv;
-
-  // True if the "pv" value exists and isn't empty.
-  return key.Open(installer_state.root_key(),
-                  install_static::GetBinariesClientsKeyPath().c_str(),
-                  KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS &&
-         key.ReadValue(google_update::kRegVersionField, &pv) == ERROR_SUCCESS &&
-         !pv.empty();
-}
-
 void DoLegacyCleanups(const InstallerState& installer_state,
                       InstallStatus install_status) {
   // Do no harm if the install didn't succeed.
diff --git a/chrome/installer/setup/setup_util.h b/chrome/installer/setup/setup_util.h
index c389948..a5b2cd8 100644
--- a/chrome/installer/setup/setup_util.h
+++ b/chrome/installer/setup/setup_util.h
@@ -130,10 +130,6 @@
 // De-register Chrome's EventLog message provider dll.
 void DeRegisterEventLogProvider();
 
-// Returns true if the now-deprecated multi-install binaries are registered as
-// an installed product with Google Update.
-bool AreBinariesInstalled(const InstallerState& installer_state);
-
 // Removes leftover bits from features that have been removed from the product.
 void DoLegacyCleanups(const InstallerState& installer_state,
                       InstallStatus install_status);
diff --git a/chrome/installer/setup/setup_util_unittest.cc b/chrome/installer/setup/setup_util_unittest.cc
index 71bc2a36..f3eed69 100644
--- a/chrome/installer/setup/setup_util_unittest.cc
+++ b/chrome/installer/setup/setup_util_unittest.cc
@@ -437,8 +437,6 @@
 TEST(SetupUtilTest, ContainsUnsupportedSwitch) {
   EXPECT_FALSE(installer::ContainsUnsupportedSwitch(
       base::CommandLine::FromString(L"foo.exe")));
-  EXPECT_FALSE(installer::ContainsUnsupportedSwitch(
-      base::CommandLine::FromString(L"foo.exe --multi-install --chrome")));
   EXPECT_TRUE(installer::ContainsUnsupportedSwitch(
       base::CommandLine::FromString(L"foo.exe --chrome-frame")));
 }
diff --git a/chrome/installer/util/channel_info.cc b/chrome/installer/util/channel_info.cc
index f5c9a58..8fe202fb 100644
--- a/chrome/installer/util/channel_info.cc
+++ b/chrome/installer/util/channel_info.cc
@@ -16,43 +16,19 @@
 
 namespace {
 
-const wchar_t kModChrome[] = L"-chrome";
-const wchar_t kModChromeFrame[] = L"-chromeframe";
-const wchar_t kModAppHostDeprecated[] = L"-apphost";
-const wchar_t kModAppLauncherDeprecated[] = L"-applauncher";
-const wchar_t kModMultiInstall[] = L"-multi";
-const wchar_t kModReadyMode[] = L"-readymode";
 const wchar_t kModStage[] = L"-stage:";
 const wchar_t kModStatsDefault[] = L"-statsdef_";
 const wchar_t kSfxFull[] = L"-full";
-const wchar_t kSfxMigrating[] = L"-migrating";
-const wchar_t kSfxMultiFail[] = L"-multifail";
 
 const wchar_t* const kModifiers[] = {
     kModStatsDefault,
     kModStage,
-    kModMultiInstall,
-    kModChrome,
-    kModChromeFrame,
-    kModAppHostDeprecated,
-    kModAppLauncherDeprecated,
-    kModReadyMode,
-    kSfxMultiFail,
-    kSfxMigrating,
     kSfxFull,
 };
 
 enum ModifierIndex {
   MOD_STATS_DEFAULT,
   MOD_STAGE,
-  MOD_MULTI_INSTALL,
-  MOD_CHROME,
-  MOD_CHROME_FRAME,
-  MOD_APP_HOST_DEPRECATED,
-  MOD_APP_LAUNCHER_DEPRECATED,
-  MOD_READY_MODE,
-  SFX_MULTI_FAIL,
-  SFX_MIGRATING,
   SFX_FULL,
   NUM_MODIFIERS
 };
@@ -177,51 +153,6 @@
   return true;
 }
 
-bool ChannelInfo::IsChrome() const {
-  return HasModifier(MOD_CHROME, value_);
-}
-
-bool ChannelInfo::SetChrome(bool value) {
-  return SetModifier(MOD_CHROME, value, &value_);
-}
-
-bool ChannelInfo::IsChromeFrame() const {
-  return HasModifier(MOD_CHROME_FRAME, value_);
-}
-
-bool ChannelInfo::SetChromeFrame(bool value) {
-  return SetModifier(MOD_CHROME_FRAME, value, &value_);
-}
-
-bool ChannelInfo::IsAppLauncher() const {
-  return HasModifier(MOD_APP_LAUNCHER_DEPRECATED, value_);
-}
-
-bool ChannelInfo::SetAppLauncher(bool value) {
-  // Unconditionally remove -apphost since it has been long deprecated.
-  bool changed_app_host = SetModifier(MOD_APP_HOST_DEPRECATED, false, &value_);
-  // Set value for -applauncher, relying on caller for policy.
-  bool changed_app_launcher =
-      SetModifier(MOD_APP_LAUNCHER_DEPRECATED, value, &value_);
-  return changed_app_host || changed_app_launcher;
-}
-
-bool ChannelInfo::IsMultiInstall() const {
-  return HasModifier(MOD_MULTI_INSTALL, value_);
-}
-
-bool ChannelInfo::SetMultiInstall(bool value) {
-  return SetModifier(MOD_MULTI_INSTALL, value, &value_);
-}
-
-bool ChannelInfo::IsReadyMode() const {
-  return HasModifier(MOD_READY_MODE, value_);
-}
-
-bool ChannelInfo::SetReadyMode(bool value) {
-  return SetModifier(MOD_READY_MODE, value, &value_);
-}
-
 bool ChannelInfo::ClearStage() {
   base::string16::size_type position;
   base::string16::size_type length;
@@ -244,31 +175,4 @@
   return SetModifier(SFX_FULL, value, &value_);
 }
 
-bool ChannelInfo::HasMultiFailSuffix() const {
-  return HasModifier(SFX_MULTI_FAIL, value_);
-}
-
-bool ChannelInfo::SetMultiFailSuffix(bool value) {
-  return SetModifier(SFX_MULTI_FAIL, value, &value_);
-}
-
-bool ChannelInfo::SetMigratingSuffix(bool value) {
-  return SetModifier(SFX_MIGRATING, value, &value_);
-}
-
-bool ChannelInfo::HasMigratingSuffix() const {
-  return HasModifier(SFX_MIGRATING, value_);
-}
-
-bool ChannelInfo::RemoveAllModifiersAndSuffixes() {
-  bool modified = false;
-
-  for (int scan = 0; scan < NUM_MODIFIERS; ++scan) {
-    ModifierIndex index = static_cast<ModifierIndex>(scan);
-    modified = SetModifier(index, false, &value_) || modified;
-  }
-
-  return modified;
-}
-
 }  // namespace installer
diff --git a/chrome/installer/util/channel_info.h b/chrome/installer/util/channel_info.h
index 331f314..b59704b 100644
--- a/chrome/installer/util/channel_info.h
+++ b/chrome/installer/util/channel_info.h
@@ -37,42 +37,6 @@
     return value_ == other.value_;
   }
 
-  // Returns true if the -chrome modifier is present in the value.
-  bool IsChrome() const;
-
-  // Adds or removes the -chrome modifier, returning true if the value is
-  // modified.
-  bool SetChrome(bool value);
-
-  // Returns true if the -chromeframe modifier is present in the value.
-  bool IsChromeFrame() const;
-
-  // Adds or removes the -chromeframe modifier, returning true if the value is
-  // modified.
-  bool SetChromeFrame(bool value);
-
-  // (Deprecated) Returns true if the -applauncher modifier is present in the
-  // value.
-  bool IsAppLauncher() const;
-
-  // (Deprecated) Adds or removes the -applauncher modifier, returning true if
-  // the value is modified.
-  bool SetAppLauncher(bool value);
-
-  // Returns true if the -multi modifier is present in the value.
-  bool IsMultiInstall() const;
-
-  // Adds or removes the -multi modifier, returning true if the value is
-  // modified.
-  bool SetMultiInstall(bool value);
-
-  // Returns true if the -readymode modifier is present in the value.
-  bool IsReadyMode() const;
-
-  // Adds or removes the -readymode modifier, returning true if the value is
-  // modified.
-  bool SetReadyMode(bool value);
-
   // Removes the -stage: modifier, returning true if the value is modified.
   bool ClearStage();
 
@@ -88,24 +52,6 @@
   // modified.
   bool SetFullSuffix(bool value);
 
-  // Returns true if the -multifail suffix is present in the value.
-  bool HasMultiFailSuffix() const;
-
-  // Adds or removes the -multifail suffix, returning true if the value is
-  // modified.
-  bool SetMultiFailSuffix(bool value);
-
-  // Adds or removes the -migrating suffix, returning true if the value is
-  // modified.
-  bool SetMigratingSuffix(bool value);
-
-  // Returns true if the -migrating suffix is present in the value.
-  bool HasMigratingSuffix() const;
-
-  // Removes all modifiers and suffixes. For example, 2.0-dev-multi-chrome-full
-  // becomes 2.0-dev. Returns true if the value is modified.
-  bool RemoveAllModifiersAndSuffixes();
-
  private:
   base::string16 value_;
 };  // class ChannelInfo
diff --git a/chrome/installer/util/channel_info_unittest.cc b/chrome/installer/util/channel_info_unittest.cc
index f041cf2..6cb06020 100644
--- a/chrome/installer/util/channel_info_unittest.cc
+++ b/chrome/installer/util/channel_info_unittest.cc
@@ -43,79 +43,6 @@
   EXPECT_EQ(L"2.0-beta", ci.value());
 }
 
-TEST(ChannelInfoTest, MultiInstall) {
-  ChannelInfo ci;
-
-  ci.set_value(L"");
-  EXPECT_TRUE(ci.SetMultiInstall(true));
-  EXPECT_TRUE(ci.IsMultiInstall());
-  EXPECT_EQ(L"-multi", ci.value());
-  EXPECT_FALSE(ci.SetMultiInstall(true));
-  EXPECT_TRUE(ci.IsMultiInstall());
-  EXPECT_EQ(L"-multi", ci.value());
-  EXPECT_TRUE(ci.SetMultiInstall(false));
-  EXPECT_FALSE(ci.IsMultiInstall());
-  EXPECT_EQ(L"", ci.value());
-  EXPECT_FALSE(ci.SetMultiInstall(false));
-  EXPECT_FALSE(ci.IsMultiInstall());
-  EXPECT_EQ(L"", ci.value());
-
-  ci.set_value(L"2.0-beta");
-  EXPECT_TRUE(ci.SetMultiInstall(true));
-  EXPECT_TRUE(ci.IsMultiInstall());
-  EXPECT_EQ(L"2.0-beta-multi", ci.value());
-  EXPECT_FALSE(ci.SetMultiInstall(true));
-  EXPECT_TRUE(ci.IsMultiInstall());
-  EXPECT_EQ(L"2.0-beta-multi", ci.value());
-  EXPECT_TRUE(ci.SetMultiInstall(false));
-  EXPECT_FALSE(ci.IsMultiInstall());
-  EXPECT_EQ(L"2.0-beta", ci.value());
-  EXPECT_FALSE(ci.SetMultiInstall(false));
-  EXPECT_FALSE(ci.IsMultiInstall());
-  EXPECT_EQ(L"2.0-beta", ci.value());
-}
-
-TEST(ChannelInfoTest, Migration) {
-  ChannelInfo ci;
-
-  ci.set_value(L"");
-  EXPECT_TRUE(ci.SetMigratingSuffix(true));
-  EXPECT_TRUE(ci.HasMigratingSuffix());
-  EXPECT_EQ(L"-migrating", ci.value());
-  EXPECT_FALSE(ci.SetMigratingSuffix(true));
-  EXPECT_TRUE(ci.HasMigratingSuffix());
-  EXPECT_EQ(L"-migrating", ci.value());
-  EXPECT_TRUE(ci.SetMigratingSuffix(false));
-  EXPECT_FALSE(ci.HasMigratingSuffix());
-  EXPECT_EQ(L"", ci.value());
-  EXPECT_FALSE(ci.SetMigratingSuffix(false));
-  EXPECT_FALSE(ci.HasMigratingSuffix());
-  EXPECT_EQ(L"", ci.value());
-
-  ci.set_value(L"2.0-beta");
-  EXPECT_TRUE(ci.SetMigratingSuffix(true));
-  EXPECT_TRUE(ci.HasMigratingSuffix());
-  EXPECT_EQ(L"2.0-beta-migrating", ci.value());
-  EXPECT_FALSE(ci.SetMigratingSuffix(true));
-  EXPECT_TRUE(ci.HasMigratingSuffix());
-  EXPECT_EQ(L"2.0-beta-migrating", ci.value());
-  EXPECT_TRUE(ci.SetMigratingSuffix(false));
-  EXPECT_FALSE(ci.HasMigratingSuffix());
-  EXPECT_EQ(L"2.0-beta", ci.value());
-  EXPECT_FALSE(ci.SetMigratingSuffix(false));
-  EXPECT_FALSE(ci.HasMigratingSuffix());
-  EXPECT_EQ(L"2.0-beta", ci.value());
-}
-
-TEST(ChannelInfoTest, Combinations) {
-  ChannelInfo ci;
-
-  ci.set_value(L"2.0-beta-chromeframe");
-  EXPECT_FALSE(ci.IsChrome());
-  ci.set_value(L"2.0-beta-chromeframe-chrome");
-  EXPECT_TRUE(ci.IsChrome());
-}
-
 TEST(ChannelInfoTest, ClearStage) {
   ChannelInfo ci;
 
@@ -169,15 +96,3 @@
     }
   }
 }
-
-TEST(ChannelInfoTest, RemoveAllModifiersAndSuffixes) {
-  ChannelInfo ci;
-
-  ci.set_value(L"");
-  EXPECT_FALSE(ci.RemoveAllModifiersAndSuffixes());
-  EXPECT_EQ(L"", ci.value());
-
-  ci.set_value(L"2.0-dev-multi-chrome-chromeframe-migrating");
-  EXPECT_TRUE(ci.RemoveAllModifiersAndSuffixes());
-  EXPECT_EQ(L"2.0-dev", ci.value());
-}
diff --git a/chrome/installer/util/fake_product_state.h b/chrome/installer/util/fake_product_state.h
index c1819165..a5e7fc3 100644
--- a/chrome/installer/util/fake_product_state.h
+++ b/chrome/installer/util/fake_product_state.h
@@ -15,7 +15,6 @@
  public:
   // Takes ownership of |version|.
   void set_version(base::Version* version) { version_.reset(version); }
-  void set_multi_install(bool multi) { multi_install_ = multi; }
   void set_brand(const std::wstring& brand) { brand_ = brand; }
   void set_usagestats(DWORD usagestats) {
     has_usagestats_ = true;
diff --git a/chrome/installer/util/google_update_settings.cc b/chrome/installer/util/google_update_settings.cc
index 350eb83..b6375be 100644
--- a/chrome/installer/util/google_update_settings.cc
+++ b/chrome/installer/util/google_update_settings.cc
@@ -259,13 +259,6 @@
   return (result == ERROR_SUCCESS);
 }
 
-google_update::Tristate
-GoogleUpdateSettings::GetCollectStatsConsentForBinaries() {
-  return GetCollectStatsConsentImpl(
-      &install_static::GetClientStateKeyPathForBinaries,
-      &install_static::GetClientStateMediumKeyPathForBinaries);
-}
-
 // static
 bool GoogleUpdateSettings::GetCollectStatsConsentDefault(
     bool* stats_consent_default) {
@@ -479,16 +472,6 @@
     DCHECK_EQ(installer::UNKNOWN_ARCHIVE_TYPE, archive_type);
   }
 
-  // The mini_installer in Chrome 10 through 12 added "-multifail" to the "ap"
-  // value if "--multi-install" was on the command line. Unconditionally remove
-  // it if present.
-  // TODO(grt): Move this cleanup into mini_installer.cc's SetInstallerFlags.
-  if (value->SetMultiFailSuffix(false)) {
-    VLOG(1) << "Removed multi-install failure key; switching to channel: "
-            << value->value();
-    modified = true;
-  }
-
   if (value->ClearStage()) {
     VLOG(1) << "Removed (legacy) stage information; switching to channel: "
             << value->value();
diff --git a/chrome/installer/util/google_update_settings.h b/chrome/installer/util/google_update_settings.h
index 054df738..159c56bc 100644
--- a/chrome/installer/util/google_update_settings.h
+++ b/chrome/installer/util/google_update_settings.h
@@ -94,10 +94,6 @@
   static bool SetCollectStatsConsent(bool consented);
 
 #if defined(OS_WIN)
-  // Returns whether the user has given consent to collect UMA data and send
-  // crash dumps to Google for the deprecated Chrome binaries.
-  static google_update::Tristate GetCollectStatsConsentForBinaries();
-
   // Returns the default (original) state of the "send usage stats" checkbox
   // shown to the user when they downloaded Chrome. The value is returned via
   // the out parameter |stats_consent_default|. This function returns true if
@@ -178,8 +174,6 @@
   // - If we are currently running full installer, we remove this magic
   // string (if it is present) regardless of whether installer failed or not.
   // There is no fall-back for full installer :)
-  // - Unconditionally remove "-multifail" since we haven't crashed.
-  // |state_key| should be obtained via InstallerState::state_key().
   // - Unconditionally clear a legacy "-stage:" modifier.
   static void UpdateInstallStatus(bool system_install,
                                   installer::ArchiveType archive_type,
@@ -202,7 +196,7 @@
   //   not present already).
   // - If full installer failed, still remove this magic
   //   string (if it is present already).
-  // Additionally, any legacy "-multifail" or "-stage:*" values are
+  // Additionally, any legacy ""-stage:*" values are
   // unconditionally removed.
   //
   // archive_type: tells whether this is incremental install or not.
@@ -220,11 +214,8 @@
                                          bool* is_overridden);
 
   // Returns true if Chrome should be updated automatically by Google Update
-  // based on current autoupdate settings. This is distinct from
-  // GetAppUpdatePolicy (which checks only the policy for a given app), as it
-  // checks for general Google Update configuration as well as multi-install
-  // Chrome. Note that for Chromium builds, this returns false since Chromium is
-  // assumed not to autoupdate.
+  // based on current autoupdate settings. Note that for Chromium builds, this
+  // returns false since Chromium is assumed not to autoupdate.
   static bool AreAutoupdatesEnabled();
 
   // Attempts to reenable auto-updates for Chrome by removing any group policy
diff --git a/chrome/installer/util/google_update_settings_unittest.cc b/chrome/installer/util/google_update_settings_unittest.cc
index 0619ae1..d0275c0 100644
--- a/chrome/installer/util/google_update_settings_unittest.cc
+++ b/chrome/installer/util/google_update_settings_unittest.cc
@@ -245,26 +245,7 @@
     L"1.1-dev-full"
   };
   static_assert(base::size(full) == base::size(plain), "bad full array size");
-  const wchar_t* const multifail[] = {
-    L"-multifail",
-    L"1.1-multifail",
-    L"1.1-dev-multifail"
-  };
-  static_assert(base::size(multifail) == base::size(plain),
-                "bad multifail array size");
-  const wchar_t* const multifail_full[] = {
-    L"-multifail-full",
-    L"1.1-multifail-full",
-    L"1.1-dev-multifail-full"
-  };
-  static_assert(base::size(multifail_full) == base::size(plain),
-                "bad multifail_full array size");
-  const wchar_t* const* input_arrays[] = {
-    plain,
-    full,
-    multifail,
-    multifail_full
-  };
+  const wchar_t* const* input_arrays[] = {plain, full};
   ChannelInfo v;
   for (const installer::ArchiveType archive_type : archive_types) {
     for (const int result : results) {
@@ -284,8 +265,7 @@
       for (const wchar_t* const* inputs : input_arrays) {
         if (archive_type == installer::UNKNOWN_ARCHIVE_TYPE) {
           // "-full" is untouched if the archive type is unknown.
-          // "-multifail" is unconditionally removed.
-          if (inputs == full || inputs == multifail_full)
+          if (inputs == full)
             outputs = full;
           else
             outputs = plain;
diff --git a/chrome/installer/util/installation_state.cc b/chrome/installer/util/installation_state.cc
index 68deaa00..a3d9446 100644
--- a/chrome/installer/util/installation_state.cc
+++ b/chrome/installer/util/installation_state.cc
@@ -40,7 +40,6 @@
       eula_accepted_(0),
       usagestats_(0),
       msi_(false),
-      multi_install_(false),
       has_eula_accepted_(false),
       has_oem_install_(false),
       has_usagestats_(false) {
@@ -114,9 +113,6 @@
     DWORD dw_value = 0;
     msi_ = (key.ReadValueDW(google_update::kRegMSIField,
                             &dw_value) == ERROR_SUCCESS) && (dw_value != 0);
-    // Multi-install is a legacy option that is read for the sole purpose of
-    // migrating clients away from it.
-    multi_install_ = uninstall_command_.HasSwitch("multi-install");
   }
 
   // Read from the ClientStateMedium key.  Values here override those in
@@ -166,7 +162,6 @@
   eula_accepted_ = other.eula_accepted_;
   usagestats_ = other.usagestats_;
   msi_ = other.msi_;
-  multi_install_ = other.multi_install_;
   has_eula_accepted_ = other.has_eula_accepted_;
   has_oem_install_ = other.has_oem_install_;
   has_usagestats_ = other.has_usagestats_;
@@ -186,7 +181,6 @@
   eula_accepted_ = 0;
   usagestats_ = 0;
   msi_ = false;
-  multi_install_ = false;
   has_eula_accepted_ = false;
   has_oem_install_ = false;
   has_usagestats_ = false;
diff --git a/chrome/installer/util/installation_state.h b/chrome/installer/util/installation_state.h
index efc7ea0..324d9f2 100644
--- a/chrome/installer/util/installation_state.h
+++ b/chrome/installer/util/installation_state.h
@@ -83,9 +83,6 @@
     return uninstall_command_;
   }
 
-  // True if |uninstall_command| contains --multi-install.
-  bool is_multi_install() const { return multi_install_; }
-
   // Returns the set of Google Update commands.
   const AppCommands& commands() const { return commands_; }
 
@@ -107,7 +104,6 @@
   DWORD eula_accepted_;
   DWORD usagestats_;
   bool msi_ : 1;
-  bool multi_install_ : 1;
   bool has_eula_accepted_ : 1;
   bool has_oem_install_ : 1;
   bool has_usagestats_ : 1;
diff --git a/chrome/installer/util/master_preferences.cc b/chrome/installer/util/master_preferences.cc
index df8a6e2..435c7606 100644
--- a/chrome/installer/util/master_preferences.cc
+++ b/chrome/installer/util/master_preferences.cc
@@ -164,12 +164,6 @@
     }
   }
 
-  // Strip multi-install from the dictionary, if present. This ensures that any
-  // code that probes the dictionary directly to check for multi-install
-  // receives false. The updated dictionary is not written back to disk.
-  master_dictionary_->Remove(
-      std::string(master_preferences::kDistroDict) + ".multi_install", nullptr);
-
   // Cache a pointer to the distribution dictionary. Ignore errors if any.
   master_dictionary_->GetDictionary(installer::master_preferences::kDistroDict,
                                     &distribution_);
diff --git a/chrome/installer/util/product_state_unittest.cc b/chrome/installer/util/product_state_unittest.cc
index 259aeb5..5c8a6974 100644
--- a/chrome/installer/util/product_state_unittest.cc
+++ b/chrome/installer/util/product_state_unittest.cc
@@ -342,36 +342,6 @@
   }
 }
 
-// Test detection of multi-install.
-TEST_P(ProductStateTest, InitializeMultiInstall) {
-  MinimallyInstallProduct(L"10.0.1.1");
-
-  // No uninstall command means single install.
-  {
-    ProductState state;
-    ApplyUninstallCommand(NULL, NULL);
-    EXPECT_TRUE(state.Initialize(system_install_));
-    EXPECT_FALSE(state.is_multi_install());
-  }
-
-  // Uninstall command without --multi-install is single install.
-  {
-    ProductState state;
-    ApplyUninstallCommand(L"setup.exe", L"--uninstall");
-    EXPECT_TRUE(state.Initialize(system_install_));
-    EXPECT_FALSE(state.is_multi_install());
-  }
-
-  // Uninstall command with --multi-install is multi install.
-  {
-    ProductState state;
-    ApplyUninstallCommand(L"setup.exe",
-                          L"--uninstall --chrome --multi-install");
-    EXPECT_TRUE(state.Initialize(system_install_));
-    EXPECT_TRUE(state.is_multi_install());
-  }
-}
-
 INSTANTIATE_TEST_SUITE_P(UserLevel, ProductStateTest, ::testing::Values(false));
 INSTANTIATE_TEST_SUITE_P(SystemLevel,
                          ProductStateTest,
diff --git a/chrome/installer/util/util_constants.h b/chrome/installer/util/util_constants.h
index a6f41d10..b1f54209 100644
--- a/chrome/installer/util/util_constants.h
+++ b/chrome/installer/util/util_constants.h
@@ -137,7 +137,6 @@
   CREATING_VISUAL_MANIFEST,  // Creating VisualElementsManifest.xml.
   BUILDING,                  // Building the install work item list.
   EXECUTING,                 // Executing the install work item list.
-  UPDATING_CHANNELS,         // Updating channel information.
   COPYING_PREFERENCES_FILE,  // Copying preferences file.
   CREATING_SHORTCUTS,        // Creating shortcuts.
   REGISTERING_CHROME,        // Performing Chrome registration.
diff --git a/chrome/services/local_search_service/index_impl.cc b/chrome/services/local_search_service/index_impl.cc
index 4d48712a..651c786a 100644
--- a/chrome/services/local_search_service/index_impl.cc
+++ b/chrome/services/local_search_service/index_impl.cc
@@ -73,6 +73,9 @@
 
 }  // namespace
 
+local_search_service::Data::Data(const std::string& id,
+                                 const std::vector<base::string16>& search_tags)
+    : id(id), search_tags(search_tags) {}
 local_search_service::Data::Data() = default;
 local_search_service::Data::Data(const Data& data) = default;
 local_search_service::Data::~Data() = default;
diff --git a/chrome/services/local_search_service/index_impl.h b/chrome/services/local_search_service/index_impl.h
index 75eff5d4fe..d000fde8 100644
--- a/chrome/services/local_search_service/index_impl.h
+++ b/chrome/services/local_search_service/index_impl.h
@@ -34,6 +34,8 @@
 
   // Data item will be matched between its search tags and query term.
   std::vector<base::string16> search_tags;
+
+  Data(const std::string& id, const std::vector<base::string16>& search_tags);
   Data();
   Data(const Data& data);
   ~Data();
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index f01bf92..aece2fa1 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -247,6 +247,7 @@
       # target.
       "//chrome/app:test_support",
       "//components/ukm:test_support",
+      "//components/ukm:ukm_test_helper",
       "//components/zoom:test_support",
     ]
     sources += [
@@ -511,6 +512,7 @@
       "../browser/metrics/metrics_service_user_demographics_browsertest.cc",
       "../browser/net/cert_verify_proc_browsertest.cc",
       "../browser/profiles/profile_browsertest_android.cc",
+      "../browser/ssl/chrome_security_state_client_browsertest.cc",
       "../browser/ssl/crlset_browsertest.cc",
       "android/browsertests_apk/android_browsertests_jni_onload.cc",
       "base/android/android_browser_test_browsertest_android.cc",
@@ -1166,6 +1168,7 @@
       "../browser/ui/passwords/google_password_manager_navigation_throttle_browsertest.cc",
       "../browser/ui/tabs/pinned_tab_service_browsertest.cc",
       "../browser/ui/thumbnails/thumbnail_tab_helper_browsertest.cc",
+      "../browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc",
       "../browser/ui/views/sharing/click_to_call_browsertest.cc",
       "../browser/ui/views/sharing/remote_copy_browsertest.cc",
       "../browser/ui/views/sharing/shared_clipboard_browsertest.cc",
@@ -2314,6 +2317,8 @@
         "../browser/chromeos/policy/component_active_directory_policy_browsertest.cc",
         "../browser/chromeos/policy/device_cloud_external_data_policy_observer_browsertest.cc",
         "../browser/chromeos/policy/device_cloud_policy_browsertest.cc",
+        "../browser/chromeos/policy/device_display_cros_browser_test.cc",
+        "../browser/chromeos/policy/device_display_cros_browser_test.h",
         "../browser/chromeos/policy/device_local_account_browsertest.cc",
         "../browser/chromeos/policy/device_login_screen_policy_browsertest.cc",
         "../browser/chromeos/policy/device_policy_cloud_external_data_manager_browsertest.cc",
@@ -3982,15 +3987,7 @@
       "../browser/resource_coordinator/usage_clock_unittest.cc",
 
       # The site data database isn't supported on Android.
-      "../browser/performance_manager/persistence/site_data/exponential_moving_average_unittest.cc",
-      "../browser/performance_manager/persistence/site_data/leveldb_site_data_store_unittest.cc",
-      "../browser/performance_manager/persistence/site_data/non_recording_site_data_cache_unittest.cc",
       "../browser/performance_manager/persistence/site_data/site_data_cache_facade_unittest.cc",
-      "../browser/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc",
-      "../browser/performance_manager/persistence/site_data/site_data_cache_impl_unittest.cc",
-      "../browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc",
-      "../browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc",
-      "../browser/performance_manager/persistence/site_data/site_data_writer_unittest.cc",
       "../browser/performance_manager/persistence/site_data/unittest_utils.cc",
       "../browser/performance_manager/persistence/site_data/unittest_utils.h",
 
@@ -5809,6 +5806,7 @@
       "//components/sync",
       "//components/sync:test_support_model",
       "//components/ukm:test_support",
+      "//components/ukm:ukm_test_helper",
       "//content/app/resources",
       "//content/test:test_support",
       "//crypto:platform",
diff --git a/chrome/test/base/extension_js_browser_test.cc b/chrome/test/base/extension_js_browser_test.cc
index 1a68bef6..fc2a03a 100644
--- a/chrome/test/base/extension_js_browser_test.cc
+++ b/chrome/test/base/extension_js_browser_test.cc
@@ -14,11 +14,9 @@
 #include "content/public/test/browser_test_utils.h"
 #include "extensions/browser/browsertest_util.h"
 
-ExtensionJSBrowserTest::ExtensionJSBrowserTest() : libs_loaded_(false) {
-}
+ExtensionJSBrowserTest::ExtensionJSBrowserTest() : libs_loaded_(false) {}
 
-ExtensionJSBrowserTest::~ExtensionJSBrowserTest() {
-}
+ExtensionJSBrowserTest::~ExtensionJSBrowserTest() {}
 
 void ExtensionJSBrowserTest::WaitForExtension(const char* extension_id,
                                               const base::Closure& load_cb) {
@@ -36,10 +34,22 @@
   args.push_back(base::Value(test_fixture));
   args.push_back(base::Value(test_name));
   std::vector<base::string16> scripts;
+
+  base::Value test_runner_params(base::Value::Type::DICTIONARY);
+  if (embedded_test_server()->Started()) {
+    test_runner_params.SetKey(
+        "testServerBaseUrl",
+        base::Value(embedded_test_server()->base_url().spec()));
+  }
+
   if (!libs_loaded_) {
     BuildJavascriptLibraries(&scripts);
     libs_loaded_ = true;
   }
+
+  scripts.push_back(base::UTF8ToUTF16(content::JsReplace(
+      "const testRunnerParams = $1;", std::move(test_runner_params))));
+
   scripts.push_back(
       BuildRunTestJSCall(is_async, "RUN_TEST_F", std::move(args)));
 
@@ -50,8 +60,7 @@
   std::string result =
       extensions::browsertest_util::ExecuteScriptInBackgroundPage(
           Profile::FromBrowserContext(load_waiter_->browser_context()),
-          load_waiter_->extension_id(),
-          script);
+          load_waiter_->extension_id(), script);
 
   std::unique_ptr<base::Value> value_result =
       base::JSONReader::ReadDeprecated(result);
diff --git a/chrome/test/base/js2gtest.js b/chrome/test/base/js2gtest.js
index 6939664e..3710756 100644
--- a/chrome/test/base/js2gtest.js
+++ b/chrome/test/base/js2gtest.js
@@ -396,6 +396,7 @@
       resolveClosureModuleDeps(this[testFixture].prototype.closureModuleDeps),
       [testFile]);
   const testFLine = getTestDeclarationLineNumber();
+  const testServer = this[testFixture].prototype.testServer;
 
   if (typedefCppFixture && !typedeffedCppFixtures.has(testFixture)) {
     const switches = this[testFixture].prototype.commandLineSwitches;
@@ -413,7 +414,7 @@
       output(`
 class ${testFixture} : public ${typedefCppFixture} {
  protected:`);
-      if (featureList || featuresWithParameters) {
+      if (featureList || featuresWithParameters || testServer) {
         output(`
   ${testFixture}() {`);
         if (featureList) {
@@ -465,6 +466,10 @@
     });`);
           }
         }
+        if (testServer) {
+          output(`
+    ignore_result(embedded_test_server()->Start());`);
+        }
         output(`
   }`);
       } else {
diff --git a/chrome/test/data/android/render_tests/NewTabPageTest.focus_fake_box.Nexus_5X-23.png.sha1 b/chrome/test/data/android/render_tests/NewTabPageTest.focus_fake_box.Nexus_5X-23.png.sha1
deleted file mode 100644
index 6389072c..0000000
--- a/chrome/test/data/android/render_tests/NewTabPageTest.focus_fake_box.Nexus_5X-23.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-978cf88f5f04f0182dec99909ac1ee924b6459f3
\ No newline at end of file
diff --git a/chrome/test/data/extensions/api_test/README.txt b/chrome/test/data/extensions/api_test/README.txt
deleted file mode 100644
index 12574aa..0000000
--- a/chrome/test/data/extensions/api_test/README.txt
+++ /dev/null
@@ -1,92 +0,0 @@
-This directory contains extensions which are unit tests for the extension API.
-These tests are written using the extension API test framework, which allows
-us to do end-to-end testing of extension API in a browser_test.  The general way
-these tests work is to run code in an extension when they're loaded and to post
-a pass or fail notification back up to the C++ unit test which then reports the
-success or failure.  In the common case, the extension runs many subtests and
-then reports up a single pass or fail.  This case is made easy by the test
-framework.
-
-To write a new test:
-
-(1) Add a new browser_test which is a subclass of ExtensionApiTest.  This test
-should call RunExtensionTest("extension_name") to kick off the test.  See
-bookmark_extension_apitest.cc for an example.
-
-(2) Create an extension of in this directory of the same name as the extension
-that your test referred to ("extension_name" above).  This test should load
-a background page which immediately starts its test.
-
-(3) In your extension page, call chrome.test.runTests with an array of
-functions which represent your subtests.  Each of these functions will most
-likely call one or more async extension APIs.  Wrap the callback for each of
-these API calls with chrome.test.callbackPass or chrome.test.callbackFail
-depending on whether or not you're expecting the callback to generate an error
-or not.  That's it.  The test framework notices when each of these callbacks
-is registered and keeps a count of what's expected.  When the right number of
-callbacks has fired, that test function will be marked as passed or failed and
-the next one will be called.  Some other useful helper functions you'll use are
-chrome.test.assertTrue(expr, message), chrome.test.assertEq(left, right) and
-chrome.test.log(message).
-
-Here's an example:
-
-chrome.test.runTests([
-  function getTree() {
-    chrome.bookmarks.getTree(chrome.test.callbackPass(function(results) {
-      chrome.test.assertTrue(compareTrees(results, expected),
-                             "getTree() result != expected");
-    }));
-  },
-
-  function get() {
-    chrome.bookmarks.get("1", chrome.test.callbackPass(function(results) {
-      chrome.test.assertTrue(compareNode(results[0], expected[0].children[0]));
-    }));
-    chrome.bookmarks.get("42", chrome.test.callbackFail("Can't find bookmark for id."));
-  },
-
-  function getArray() {
-    chrome.bookmarks.get(["1", "2"], chrome.test.callbackPass(function(results) {
-      chrome.test.assertTrue(compareNode(results[0], expected[0].children[0]),
-                             "get() result != expected");
-      chrome.test.assertTrue(compareNode(results[1], expected[0].children[1]),
-                             "get() result != expected");
-    }));
-  }
-]);
-
-// compareNode and compareTrees are helper functions that the bookmarks test
-// uses for convenience.  They're not really relevant to the framework itself.
-
-Note that chrome.test.callbackFail takes an argument which is the error message
-that it expects to get when the callback fails
-(chrome.runtime.lastError.message).
-
-Here's what the output of this test might look like:
-[==========] Running 1 test from 1 test case.
-[----------] Global test environment set-up.
-[----------] 1 test from ExtensionApiTest
-[ RUN      ] ExtensionApiTest.Bookmarks
-( RUN      ) getTree
-(  SUCCESS )
-( RUN      ) get
-(  SUCCESS )
-( RUN      ) getArray
-(  SUCCESS )
-Got EXTENSION_TEST_PASSED notification.
-[       OK ] ExtensionApiTest.DISABLED_Bookmarks (2472 ms)
-[----------] 1 test from ExtensionApiTest (2475 ms total)
-
-[----------] Global test environment tear-down
-[==========] 1 test from 1 test case ran. (2482 ms total)
-[  PASSED  ] 1 test.
-1 test run
-0 test failed
-
-Note the RUN/SUCCESS messages in () - these are the subtests that are run in
-the extension itself.  Anything printed with chrome.test.log() will also display
-in stdout of the browser test (and hence in the buildbot output for that test)
-if the VLOG level for extensions/browser/api/test/test_api.cc is at least 1,
-which can be done e.g. by adding the command-line switch --vmodule=*test_api*=1,
-see extensions/browser/api/test/test_api.cc.
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 4de54928..b470957 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -3665,6 +3665,18 @@
     ]
   },
 
+  "FloatingAccessibilityMenuEnabled": {
+    "os": ["chromeos"],
+    "pref_mappings": [
+      {
+        "pref": "settings.a11y.floating_menu",
+        "indicator_tests": [
+          { "policy": {"FloatingAccessibilityMenuEnabled": true}}
+        ]
+      }
+    ]
+  },
+
   "UserDisplayName": {
     "os": [
       "chromeos"
diff --git a/chrome/test/data/portal/portal-and-button.html b/chrome/test/data/portal/portal-and-button.html
new file mode 100644
index 0000000..ec5d4d4
--- /dev/null
+++ b/chrome/test/data/portal/portal-and-button.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<body>
+  <button>button</button>
+  <portal src="activate-portal.html"></portal>
+</body>
diff --git a/chrome/test/data/webui/chromeos/print_management/print_management_test.js b/chrome/test/data/webui/chromeos/print_management/print_management_test.js
index 386d6006..1d1b6b0b 100644
--- a/chrome/test/data/webui/chromeos/print_management/print_management_test.js
+++ b/chrome/test/data/webui/chromeos/print_management/print_management_test.js
@@ -7,7 +7,7 @@
 import 'chrome://print-management/print_management.js';
 
 import {setMetadataProviderForTesting} from 'chrome://print-management/mojo_interface_provider.js';
-import {flushTasks} from 'chrome://test/test_util.m.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 const CompletionStatus = {
   FAILED: 0,
@@ -84,6 +84,7 @@
  * @return {!Array<!HTMLElement>}
  */
 function getPrintJobEntries(page) {
+  flush();
   const entryList = page.$$('#entryList');
   return Array.from(
       entryList.querySelectorAll('print-job-entry:not([hidden])'));
@@ -197,7 +198,7 @@
     mojoApi_.setPrintJobs(printJobs);
     page = document.createElement('print-management');
     document.body.appendChild(page);
-    return flushTasks();
+    flush();
   }
 
   test('PrintHistoryListIsSortedReverseChronologically', () => {
@@ -216,13 +217,11 @@
     // Initialize with a reversed array of |expectedArr|, since we expect the
     // app to sort the list when it first loads. Since reverse() mutates the
     // original array, use a copy array to prevent mutating |expectedArr|.
-    initializePrintManagementApp(expectedArr.slice().reverse())
-        .then(() => {
-          return mojoApi_.whenCalled('getPrintJobs');
-        })
-        .then(() => {
-          verifyPrintJobs(expectedArr, getPrintJobEntries(page));
-        });
+    initializePrintManagementApp(expectedArr.slice().reverse());
+
+    mojoApi_.whenCalled('getPrintJobs').then(() => {
+      verifyPrintJobs(expectedArr, getPrintJobEntries(page));
+    });
   });
 });
 
diff --git a/chrome/test/data/webui/test_api.js b/chrome/test/data/webui/test_api.js
index 47e3f8d7..68dd84b1 100644
--- a/chrome/test/data/webui/test_api.js
+++ b/chrome/test/data/webui/test_api.js
@@ -179,6 +179,14 @@
   testShouldFail: false,
 
   /**
+   * Starts a local test server if true and injects the server's base url to
+   * each test. The url can be accessed from
+   * |testRunnerParams.testServerBaseUrl|.
+   * @type {boolean}
+   */
+  testServer: false,
+
+  /**
    * Extra libraries to add before loading this test file.
    * @type {Array<string>}
    */
diff --git a/chrome/test/enterprise/e2e/infra/installer_data b/chrome/test/enterprise/e2e/infra/installer_data
index 75f0c017..9a704b94 100644
--- a/chrome/test/enterprise/e2e/infra/installer_data
+++ b/chrome/test/enterprise/e2e/infra/installer_data
@@ -2,7 +2,6 @@
   "distribution":{

      "make_chrome_default":true,

      "system_level":true,

-     "multi_install":true,

      "verbose_logging":true,

      "chrome":true,

      "require_eula":false,

diff --git a/chrome/test/enterprise/e2e/policy/translate_enabled/translate_enabled.py b/chrome/test/enterprise/e2e/policy/translate_enabled/translate_enabled.py
index 7f62d08..2826cd1 100644
--- a/chrome/test/enterprise/e2e/policy/translate_enabled/translate_enabled.py
+++ b/chrome/test/enterprise/e2e/policy/translate_enabled/translate_enabled.py
@@ -48,4 +48,4 @@
 
   @test
   def test_TranslatedEnabledIncognito(self):
-    self.test_TranslatedEnabled(incognito=True)
+    self.test_TranslatedEnabled(incognito=True)
\ No newline at end of file
diff --git a/chrome/test/enterprise/e2e/policy/translate_enabled/translate_enabled_webdriver_test.py b/chrome/test/enterprise/e2e/policy/translate_enabled/translate_enabled_webdriver_test.py
index 6da7dc6b..41d6452f 100644
--- a/chrome/test/enterprise/e2e/policy/translate_enabled/translate_enabled_webdriver_test.py
+++ b/chrome/test/enterprise/e2e/policy/translate_enabled/translate_enabled_webdriver_test.py
@@ -2,10 +2,16 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+
+import os
 import time
-import test_util
+
 from absl import app, flags
+from selenium import webdriver
 from pywinauto.application import Application
+from pywinauto.findwindows import ElementNotFoundError
+
+import test_util
 
 # A URL that is in a different language than our Chrome language.
 URL = "https://zh.wikipedia.org/wiki/Chromium"
@@ -17,27 +23,34 @@
 
 
 def main(argv):
-  driver = test_util.create_chrome_webdriver(incognito=FLAGS.incognito)
+  os.system('start chrome --remote-debugging-port=9222')
+  options = webdriver.ChromeOptions()
+  # Add option for connecting chromedriver with Chrome
+  options.add_experimental_option("debuggerAddress", "localhost:9222")
+  driver = test_util.create_chrome_webdriver(
+      chrome_options=options, incognito=FLAGS.incognito)
   driver.get(URL)
-
   time.sleep(10)
+  translatePopupVisible = None
 
-  app = Application(backend="uia")
-  app.connect(title_re='.*Chrome|.*Chromium')
-
-  translatePopupVisible = False
-  for desc in app.top_window().descendants():
-    if 'Translate this page?' in desc.window_text():
-      translatePopupVisible = True
-      break
+  try:
+    app = Application(backend="uia")
+    app.connect(title_re='.*Chrome|.*Chromium')
+    app.top_window() \
+       .child_window(title="Translate this page?", control_type="Pane") \
+       .print_control_identifiers()
+    translatePopupVisible = True
+  except ElementNotFoundError as error:
+    translatePopupVisible = False
+  finally:
+    driver.quit()
+    os.system('taskkill /f /im chrome.exe')
 
   if translatePopupVisible:
     print "TRUE"
   else:
     print "FALSE"
 
-  driver.quit()
-
 
 if __name__ == '__main__':
   app.run(main)
diff --git a/chrome/test/mini_installer/BUILD.gn b/chrome/test/mini_installer/BUILD.gn
index 2760b51..b2d7115 100644
--- a/chrome/test/mini_installer/BUILD.gn
+++ b/chrome/test/mini_installer/BUILD.gn
@@ -19,8 +19,6 @@
                           "config/chrome_dev_no_pv.prop",
                           "config/chrome_dev_not_installed.prop",
                           "config/chrome_dev_not_inuse.prop",
-                          "config/chrome_multi_system_installed.prop",
-                          "config/chrome_multi_user_installed.prop",
                           "config/chrome_system_installed.prop",
                           "config/chrome_system_inuse.prop",
                           "config/chrome_system_no_pv.prop",
@@ -39,7 +37,6 @@
                           "config/previous_chrome_user_installed.prop",
                           "file_operations.py",
                           "launch_chrome.py",
-                          "make_chrome_multi.py",
                           "process_operations.py",
                           "property_walker.py",
                           "quit_chrome.py",
diff --git a/chrome/test/mini_installer/config/chrome_multi_system_installed.prop b/chrome/test/mini_installer/config/chrome_multi_system_installed.prop
deleted file mode 100644
index b945781..0000000
--- a/chrome/test/mini_installer/config/chrome_multi_system_installed.prop
+++ /dev/null
@@ -1,176 +0,0 @@
-{
-  "Files": {
-    "$PROGRAM_FILES\\$CHROME_DIR\\Application\\chrome.exe": {"exists": true},
-    "$PROGRAM_FILES\\$CHROME_DIR\\Application\\chrome.VisualElementsManifest.xml": {"exists": true},
-    "$PROGRAM_FILES\\$CHROME_DIR\\Application\\chrome_proxy.exe": {"exists": true},
-    "$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\chrome.dll":
-        {"exists": true},
-    "$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\chrome_elf.dll":
-        {"exists": true},
-    "$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\elevation_service.exe": {
-      "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-      "exists": true
-    },
-    "$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\elevation_service.exe": {
-      "condition": "'$CHROME_SHORT_NAME' != 'Chrome'",
-      "exists": false
-    },
-    "$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\Installer\\chrome.7z":
-        {"exists": true},
-    "$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe":
-        {"exists": true},
-    "$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION.manifest":
-        {"exists": true},
-    "$PROGRAM_FILES\\$CHROME_DIR\\Application\\$MINI_INSTALLER_FILE_VERSION":
-        {"exists": false}
-  },
-  "RegistryEntries": {
-    "HKEY_LOCAL_MACHINE\\$CHROME_UPDATE_REGISTRY_SUBKEY": {
-      "exists": "required",
-      "values": {
-        "pv": {
-          "type": "SZ",
-          "data": "$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION"
-        }
-      },
-      "wow_key": "KEY_WOW64_32KEY"
-    },
-    "HKEY_LOCAL_MACHINE\\$BINARIES_UPDATE_REGISTRY_SUBKEY": {
-      "exists": "required",
-      "values": {
-        "pv": {
-          "type": "SZ",
-          "data": "$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION"
-        }
-      },
-      "wow_key": "KEY_WOW64_32KEY"
-    },
-    "HKEY_LOCAL_MACHINE\\$LAUNCHER_UPDATE_REGISTRY_SUBKEY": {
-      "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-      "exists": "forbidden",
-      "wow_key": "KEY_WOW64_32KEY"
-    },
-    "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$CHROME_LONG_NAME": {
-      "exists": "required",
-      "values": {
-        "UninstallString": {
-          "type": "SZ",
-          "data": "\"$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe\" --uninstall --multi-install --chrome --system-level --verbose-logging"
-        },
-        "Version": {
-          "type": "SZ",
-          "data": "$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION"
-        }
-      },
-      "wow_key": "KEY_WOW64_32KEY"
-    },
-    "HKEY_LOCAL_MACHINE\\Software\\Classes\\CLSID\\$CHROME_TOAST_ACTIVATOR_CLSID\\LocalServer32": {
-      "exists": "required",
-      "values": {
-        "type": "SZ",
-        "data": "$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\notification_helper.exe"
-      }
-    },
-    "HKEY_LOCAL_MACHINE\\Software\\Classes\\CLSID\\$CHROME_ELEVATOR_CLSID": {
-      "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-      "exists": "required",
-      "values": {
-        "AppID": {
-          "type": "SZ",
-          "data": "$CHROME_ELEVATOR_CLSID"
-        }
-      }
-    },
-    "HKEY_LOCAL_MACHINE\\Software\\Classes\\AppID\\$CHROME_ELEVATOR_CLSID": {
-      "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-      "exists": "required",
-      "values": {
-        "LocalService": {
-          "type": "SZ",
-          "data": "$CHROME_ELEVATION_SERVICE_NAME"
-        }
-      }
-    },
-    "HKEY_LOCAL_MACHINE\\Software\\Classes\\Interface\\$CHROME_ELEVATOR_IID": {
-      "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-      "exists": "required"
-    },
-      "HKEY_LOCAL_MACHINE\\Software\\Classes\\Interface\\$CHROME_ELEVATOR_IID\\ProxyStubClsid32": {
-        "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-        "exists": "required",
-        "values": {
-          "type": "SZ",
-          "data": "{00020424-0000-0000-C000-000000000046}"
-        }
-      },
-      "HKEY_LOCAL_MACHINE\\Software\\Classes\\Interface\\$CHROME_ELEVATOR_IID\\TypeLib": {
-        "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-        "exists": "required",
-        "values": {
-          "type": "SZ",
-          "data": "$CHROME_ELEVATOR_IID"
-        }
-      },
-    "HKEY_LOCAL_MACHINE\\Software\\Classes\\TypeLib\\$CHROME_ELEVATOR_IID": {
-      "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-      "exists": "required"
-    },
-      "HKEY_LOCAL_MACHINE\\Software\\Classes\\TypeLib\\$CHROME_ELEVATOR_IID\\1.0": {
-        "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-        "exists": "required"
-      },
-        "HKEY_LOCAL_MACHINE\\Software\\Classes\\TypeLib\\$CHROME_ELEVATOR_IID\\1.0\\0": {
-          "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-          "exists": "required"
-        },
-          "HKEY_LOCAL_MACHINE\\Software\\Classes\\TypeLib\\$CHROME_ELEVATOR_IID\\1.0\\0\\win32": {
-            "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-            "exists": "required",
-            "values": {
-              "type": "SZ",
-              "data": "\"$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\elevation_service.exe\""
-            }
-          },
-          "HKEY_LOCAL_MACHINE\\Software\\Classes\\TypeLib\\$CHROME_ELEVATOR_IID\\1.0\\0\\win64": {
-            "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-            "exists": "required",
-            "values": {
-              "type": "SZ",
-              "data": "\"$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\elevation_service.exe\""
-            }
-          },
-    "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\$CHROME_ELEVATION_SERVICE_NAME": {
-      "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-      "exists": "required",
-      "values": {
-        "Type": {
-          "type": "DWORD",
-          "data": 16
-        },
-        "Start": {
-          "type": "DWORD",
-          "data": 3
-        },
-        "ErrorControl": {
-          "type": "DWORD",
-          "data": 1
-        },
-        "ImagePath": {
-          "type": "EXPAND_SZ",
-          "data": "\"$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\elevation_service.exe\""
-        },
-        "DisplayName": {
-          "type": "SZ",
-          "data": "$CHROME_ELEVATION_SERVICE_DISPLAY_NAME"
-        },
-        "ObjectName": {
-          "type": "SZ",
-          "data": "LocalSystem"
-        }
-      }
-    },
-    "HKEY_LOCAL_MACHINE\\Software\\Classes\\$CHROME_SHORT_NAME": {
-      "exists": "forbidden"
-    }
-  }
-}
diff --git a/chrome/test/mini_installer/config/chrome_multi_user_installed.prop b/chrome/test/mini_installer/config/chrome_multi_user_installed.prop
deleted file mode 100644
index dbf1c18..0000000
--- a/chrome/test/mini_installer/config/chrome_multi_user_installed.prop
+++ /dev/null
@@ -1,82 +0,0 @@
-{
-  "Files": {
-    "$LOCAL_APPDATA\\$CHROME_DIR\\Application\\chrome.exe": {"exists": true},
-    "$LOCAL_APPDATA\\$CHROME_DIR\\Application\\chrome.VisualElementsManifest.xml": {"exists": true},
-    "$LOCAL_APPDATA\\$CHROME_DIR\\Application\\chrome_proxy.exe": {"exists": true},
-    "$LOCAL_APPDATA\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\chrome.dll":
-        {"exists": true},
-    "$LOCAL_APPDATA\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\chrome_elf.dll":
-        {"exists": true},
-    "$LOCAL_APPDATA\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\Installer\\chrome.7z":
-        {"exists": true},
-    "$LOCAL_APPDATA\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe":
-        {"exists": true},
-    "$LOCAL_APPDATA\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION.manifest":
-        {"exists": true},
-    "$LOCAL_APPDATA\\$CHROME_DIR\\Application\\$MINI_INSTALLER_FILE_VERSION":
-        {"exists": false}
-  },
-  "RegistryEntries": {
-    "HKEY_CURRENT_USER\\$CHROME_UPDATE_REGISTRY_SUBKEY": {
-      "exists": "required",
-      "values": {
-        "pv": {
-          "type": "SZ",
-          "data": "$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION"
-        }
-      },
-      "wow_key": "KEY_WOW64_32KEY"
-    },
-    "HKEY_CURRENT_USER\\$BINARIES_UPDATE_REGISTRY_SUBKEY": {
-      "exists": "required",
-      "values": {
-        "pv": {
-          "type": "SZ",
-          "data": "$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION"
-        }
-      },
-      "wow_key": "KEY_WOW64_32KEY"
-    },
-    "HKEY_CURRENT_USER\\$LAUNCHER_UPDATE_REGISTRY_SUBKEY": {
-      "condition": "'$CHROME_SHORT_NAME' == 'Chrome'",
-      "exists": "forbidden",
-      "wow_key": "KEY_WOW64_32KEY"
-    },
-    "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$CHROME_LONG_NAME": {
-      "exists": "required",
-      "values": {
-        "UninstallString": {
-          "type": "SZ",
-          "data": "\"$LOCAL_APPDATA\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe\" --uninstall --multi-install --chrome --verbose-logging"
-        },
-        "Version": {
-          "type": "SZ",
-          "data": "$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION"
-        }
-      },
-      "wow_key": "KEY_WOW64_32KEY"
-    },
-    "HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\$CHROME_TOAST_ACTIVATOR_CLSID\\LocalServer32": {
-      "exists": "required",
-      "values": {
-        "type": "SZ",
-        "data": "$LOCAL_APPDATA\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\notification_helper.exe"
-      }
-    },
-    "HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\$CHROME_ELEVATOR_CLSID": {
-      "exists": "forbidden"
-    },
-    "HKEY_CURRENT_USER\\Software\\Classes\\AppID\\$CHROME_ELEVATOR_CLSID": {
-      "exists": "forbidden"
-    },
-    "HKEY_CURRENT_USER\\Software\\Classes\\Interface\\$CHROME_ELEVATOR_IID": {
-      "exists": "forbidden"
-    },
-    "HKEY_CURRENT_USER\\Software\\Classes\\TypeLib\\$CHROME_ELEVATOR_IID": {
-      "exists": "forbidden"
-    },
-    "HKEY_CURRENT_USER\\Software\\Classes\\$CHROME_SHORT_NAME$USER_SPECIFIC_REGISTRY_SUFFIX": {
-      "exists": "forbidden"
-    }
-  }
-}
diff --git a/chrome/test/mini_installer/config/config.config b/chrome/test/mini_installer/config/config.config
index 3f3649c..b0f97f1 100644
--- a/chrome/test/mini_installer/config/config.config
+++ b/chrome/test/mini_installer/config/config.config
@@ -130,17 +130,6 @@
         "chrome_canary_not_inuse.prop",
         "chrome_dev_not_inuse.prop",
         "chrome_system_not_inuse.prop"]],
-    ["chrome_multi_user_installed_not_inuse",
-      ["chrome_multi_user_installed.prop",
-       "chrome_beta_not_installed.prop",
-       "chrome_canary_not_installed.prop",
-       "chrome_dev_not_installed.prop",
-       "chrome_system_not_installed.prop",
-       "chrome_user_not_inuse.prop",
-       "chrome_beta_not_inuse.prop",
-       "chrome_canary_not_inuse.prop",
-       "chrome_dev_not_inuse.prop",
-       "chrome_system_not_inuse.prop"]],
     ["chrome_system_installed_not_inuse", ["chrome_user_not_installed.prop",
                                            "chrome_beta_not_installed.prop",
                                            "chrome_canary_not_installed.prop",
@@ -251,8 +240,6 @@
      "\"$MINI_INSTALLER\" \"$LOG_FILE\" --verbose-logging --do-not-launch-chrome"],
     ["install_previous_chrome_user",
      "\"$PREVIOUS_VERSION_MINI_INSTALLER\" \"$LOG_FILE\" --verbose-logging --do-not-launch-chrome"],
-    ["make_chrome_user_multi",
-     "python make_chrome_multi.py --chrome-long-name \"$CHROME_LONG_NAME\" --chrome-clients-key \"$CHROME_UPDATE_REGISTRY_SUBKEY\" --chrome-client-state-key \"$CHROME_CLIENT_STATE_KEY\" --binaries-clients-key \"$BINARIES_UPDATE_REGISTRY_SUBKEY\""],
     ["kill_user_binaries",
      "reg.exe delete \"HKEY_CURRENT_USER\\$BINARIES_UPDATE_REGISTRY_SUBKEY\" /v pv /f /reg:32"],
     ["kill_user_chrome",
@@ -393,57 +380,6 @@
         "uninstall_chrome_dev", "chrome_canary_installed_not_inuse",
         "uninstall_chrome_canary", "clean"
       ]
-    },
-    {
-      "name": "MigrateMultiSimple",
-      "description": "Verifies that an update on top of multi-install Chrome will migrate.",
-      "traversal": [
-        "no_pv",
-        "install_previous_chrome_user", "previous_chrome_user_installed_not_inuse",
-        "make_chrome_user_multi", "chrome_multi_user_installed_not_inuse",
-        "update_chrome_user", "chrome_user_updated_not_inuse",
-        "test_chrome_with_chromedriver_user", "chrome_user_installed_not_inuse",
-        "uninstall_chrome_user", "clean"
-      ]
-    },
-    {
-      "name": "MigrateMultiStrandedBinariesOnUpdate",
-      "description": "Verifies that an update on top of multi-install Chrome where Chrome is missing from the Clients key will repair and migrate.",
-      "traversal": [
-        "no_pv",
-        "install_previous_chrome_user", "previous_chrome_user_installed_not_inuse",
-        "make_chrome_user_multi", "chrome_multi_user_installed_not_inuse",
-        "kill_user_chrome", "no_chrome_user",
-        "update_chrome_user", "chrome_user_updated_not_inuse",
-        "test_chrome_with_chromedriver_user", "chrome_user_installed_not_inuse",
-        "uninstall_chrome_user", "clean"
-      ]
-    },
-    {
-      "name": "MigrateMultiStrandedBinariesOnInstall",
-      "description": "Verifies that a same-version install on top of multi-install Chrome where Chrome is missing from the Clients key will repair and migrate.",
-      "traversal": [
-        "no_pv",
-        "install_previous_chrome_user", "previous_chrome_user_installed_not_inuse",
-        "make_chrome_user_multi", "chrome_multi_user_installed_not_inuse",
-        "kill_user_chrome", "no_chrome_user",
-        "install_chrome_user", "chrome_user_installed_not_inuse",
-        "test_chrome_with_chromedriver_user", "chrome_user_installed_not_inuse",
-        "uninstall_chrome_user", "clean"
-      ]
-    },
-    {
-      "name": "MigrateMultiNoBinaries",
-      "description": "Verifies that an update on top of multi-install Chrome where the binaries are missing from the Clients key will repair and migrate.",
-      "traversal": [
-        "no_pv",
-        "install_previous_chrome_user", "previous_chrome_user_installed_not_inuse",
-        "make_chrome_user_multi", "chrome_multi_user_installed_not_inuse",
-        "kill_user_binaries", "no_chrome_user_binaries",
-        "update_chrome_user", "chrome_user_updated_not_inuse",
-        "test_chrome_with_chromedriver_user", "chrome_user_installed_not_inuse",
-        "uninstall_chrome_user", "clean"
-      ]
     }
   ]
 }
diff --git a/chrome/test/mini_installer/make_chrome_multi.py b/chrome/test/mini_installer/make_chrome_multi.py
deleted file mode 100644
index 8910a45..0000000
--- a/chrome/test/mini_installer/make_chrome_multi.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Makes an existing per-user Chrome appear to be multi-install Chrome.
-
-This script makes minimal mutations to the Windows registry to make ordinary
-single-install Chrome appear to be multi-install for purposes of testing multi-
-to single- migrations.
-"""
-
-import _winreg
-import argparse
-import sys
-
-
-def MakeChromeMulti(chrome_long_name, chrome_clients_key,
-                    chrome_client_state_key, binaries_clients_key):
-  # Update the control panel's uninstall string.
-  key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
-                        ('Software\\Microsoft\\Windows\\CurrentVersion\\'
-                         'Uninstall\\%s' % chrome_long_name), 0,
-                        _winreg.KEY_QUERY_VALUE | _winreg.KEY_SET_VALUE |
-                        _winreg.KEY_WOW64_32KEY)
-  string = _winreg.QueryValueEx(key, 'UninstallString')[0]
-  string = string.replace('--uninstall', '--uninstall --multi-install --chrome')
-  _winreg.SetValueEx(key, 'UninstallString', 0, _winreg.REG_SZ, string)
-
-  # Read Chrome's version number.
-  key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
-                        chrome_clients_key, 0,
-                        _winreg.KEY_QUERY_VALUE | _winreg.KEY_WOW64_32KEY)
-  pv = _winreg.QueryValueEx(key, 'pv')[0]
-  _winreg.CloseKey(key)
-
-  # Write that version for the binaries.
-  key = _winreg.CreateKeyEx(_winreg.HKEY_CURRENT_USER,
-                            binaries_clients_key, 0,
-                            _winreg.KEY_SET_VALUE | _winreg.KEY_WOW64_32KEY)
-  _winreg.SetValueEx(key, 'pv', 0, _winreg.REG_SZ, pv)
-  _winreg.CloseKey(key)
-
-  # Add "--multi-install --chrome" to Chrome's UninstallArguments.
-  key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
-                        chrome_client_state_key, 0,
-                        _winreg.KEY_QUERY_VALUE | _winreg.KEY_SET_VALUE |
-                        _winreg.KEY_WOW64_32KEY)
-  args = _winreg.QueryValueEx(key, 'UninstallArguments')[0]
-  args += ' --multi-install --chrome'
-  _winreg.SetValueEx(key, 'UninstallArguments', 0, _winreg.REG_SZ, args)
-
-
-def main():
-  parser = argparse.ArgumentParser(
-    description='Transforms single-install Chrome into multi-install.')
-  parser.add_argument('--chrome-long-name', default='Google Chrome',
-                      help='The full name of the product.')
-  parser.add_argument('--chrome-clients-key',
-                      default='Software\\Google\\Update\\Clients\\'
-                      '{8A69D345-D564-463c-AFF1-A69D9E530F96}',
-                      help='Chrome\'s Clients registry key path.')
-  parser.add_argument('--chrome-client-state-key',
-                      default='Software\\Google\\Update\\ClientState\\'
-                      '{8A69D345-D564-463c-AFF1-A69D9E530F96}',
-                      help='Chrome\'s ClientState registry key path.')
-  parser.add_argument('--binaries-clients-key',
-                      default='Software\\Google\\Update\\Clients\\'
-                      '{4DC8B4CA-1BDA-483e-B5FA-D3C12E15B62D}',
-                      help='Chrome Binaries\' Clients registry key path.')
-  args = parser.parse_args()
-  MakeChromeMulti(args.chrome_long_name,
-                  args.chrome_clients_key,
-                  args.chrome_client_state_key,
-                  args.binaries_clients_key)
-  return 0
-
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/chromecast/browser/cast_browser_main_parts.cc b/chromecast/browser/cast_browser_main_parts.cc
index 7e79dc4..a43a1c6c 100644
--- a/chromecast/browser/cast_browser_main_parts.cc
+++ b/chromecast/browser/cast_browser_main_parts.cc
@@ -121,6 +121,7 @@
 #if BUILDFLAG(ENABLE_CHROMECAST_EXTENSIONS)
 #include "chromecast/browser/extensions/api/tts/tts_extension_api.h"
 #include "chromecast/browser/extensions/cast_extension_system.h"
+#include "chromecast/browser/extensions/cast_extension_system_factory.h"
 #include "chromecast/browser/extensions/cast_extensions_browser_client.h"
 #include "chromecast/browser/extensions/cast_prefs.h"
 #include "chromecast/common/cast_extensions_client.h"
@@ -378,6 +379,17 @@
   }
 }
 
+#if BUILDFLAG(ENABLE_CHROMECAST_EXTENSIONS)
+// Instantiates all cast KeyedService factories, which is especially important
+// for services that should be created at profile creation time as compared to
+// lazily on first access.
+void EnsureBrowserContextKeyedServiceFactoriesBuilt() {
+  extensions::EnsureBrowserContextKeyedServiceFactoriesBuilt();
+
+  extensions::CastExtensionSystemFactory::GetInstance();
+}
+#endif
+
 }  // namespace
 
 CastBrowserMainParts::CastBrowserMainParts(
@@ -627,7 +639,7 @@
           cast_content_browser_client_->cast_network_contexts());
   extensions::ExtensionsBrowserClient::Set(extensions_browser_client_.get());
 
-  extensions::EnsureBrowserContextKeyedServiceFactoriesBuilt();
+  EnsureBrowserContextKeyedServiceFactoriesBuilt();
 
   extensions::CastExtensionSystem* extension_system =
       static_cast<extensions::CastExtensionSystem*>(
diff --git a/chromecast/browser/cast_content_browser_client.cc b/chromecast/browser/cast_content_browser_client.cc
index 3bb8274..304fb0a1 100644
--- a/chromecast/browser/cast_content_browser_client.cc
+++ b/chromecast/browser/cast_content_browser_client.cc
@@ -284,7 +284,7 @@
 }
 
 std::unique_ptr<::media::CdmFactory> CastContentBrowserClient::CreateCdmFactory(
-    service_manager::mojom::InterfaceProvider* host_interfaces) {
+    ::media::mojom::FrameInterfaceFactory* frame_interfaces) {
   return std::make_unique<media::CastCdmFactory>(GetMediaTaskRunner(),
                                                  media_resource_tracker());
 }
@@ -930,7 +930,7 @@
       std::make_unique<media::CastRenderer>(
           GetCmaBackendFactory(), std::move(media_task_runner),
           GetVideoModeSwitcher(), GetVideoResolutionPolicy(),
-          base::UnguessableToken::Create(), nullptr /* host_interfaces */),
+          base::UnguessableToken::Create(), nullptr /* frame_interfaces */),
       std::move(receiver));
 }
 
diff --git a/chromecast/browser/cast_content_browser_client.h b/chromecast/browser/cast_content_browser_client.h
index 36d167b..a079cc2 100644
--- a/chromecast/browser/cast_content_browser_client.h
+++ b/chromecast/browser/cast_content_browser_client.h
@@ -200,9 +200,8 @@
       service_manager::BinderRegistry* registry,
       blink::AssociatedInterfaceRegistry* associated_registry,
       content::RenderProcessHost* render_process_host) override;
-  void ExposeInterfacesToMediaService(
-      service_manager::BinderRegistry* registry,
-      content::RenderFrameHost* render_frame_host) override;
+  void BindMediaServiceReceiver(content::RenderFrameHost* render_frame_host,
+                                mojo::GenericPendingReceiver receiver) override;
   void RegisterBrowserInterfaceBindersForFrame(
       content::RenderFrameHost* render_frame_host,
       service_manager::BinderMapWithContext<content::RenderFrameHost*>* map)
@@ -257,7 +256,7 @@
   void CreateGeneralAudienceBrowsingService();
 
   virtual std::unique_ptr<::media::CdmFactory> CreateCdmFactory(
-      service_manager::mojom::InterfaceProvider* host_interfaces);
+      ::media::mojom::FrameInterfaceFactory* frame_interfaces);
 
 #if BUILDFLAG(ENABLE_CAST_RENDERER)
   void BindGpuHostReceiver(mojo::GenericPendingReceiver receiver) override;
diff --git a/chromecast/browser/cast_content_browser_client_receiver_bindings.cc b/chromecast/browser/cast_content_browser_client_receiver_bindings.cc
index a1f3d03..32e6af1 100644
--- a/chromecast/browser/cast_content_browser_client_receiver_bindings.cc
+++ b/chromecast/browser/cast_content_browser_client_receiver_bindings.cc
@@ -112,22 +112,29 @@
 #endif  // !defined(OS_ANDROID) && !defined(OS_FUCHSIA)
 }
 
-void CastContentBrowserClient::ExposeInterfacesToMediaService(
-    service_manager::BinderRegistry* registry,
-    content::RenderFrameHost* render_frame_host) {
-  registry->AddInterface(
-      base::BindRepeating(&CreateMediaDrmStorage, render_frame_host));
+void CastContentBrowserClient::BindMediaServiceReceiver(
+    content::RenderFrameHost* render_frame_host,
+    mojo::GenericPendingReceiver receiver) {
+  if (auto r = receiver.As<::media::mojom::MediaDrmStorage>()) {
+    CreateMediaDrmStorage(render_frame_host, std::move(r));
+    return;
+  }
 
-  registry->AddInterface(base::BindRepeating(&ServiceConnector::BindReceiver,
-                                             kMediaServiceClientId));
+  if (auto r = receiver.As<mojom::ServiceConnector>()) {
+    ServiceConnector::BindReceiver(kMediaServiceClientId, std::move(r));
+    return;
+  }
 
-  std::string application_session_id;
-  bool mixer_audio_enabled;
-  GetApplicationMediaInfo(&application_session_id, &mixer_audio_enabled,
-                          render_frame_host);
-  registry->AddInterface(base::BindRepeating(
-      &media::CreateApplicationMediaInfoManager, render_frame_host,
-      std::move(application_session_id), mixer_audio_enabled));
+  if (auto r = receiver.As<::media::mojom::CastApplicationMediaInfoManager>()) {
+    std::string application_session_id;
+    bool mixer_audio_enabled;
+    GetApplicationMediaInfo(&application_session_id, &mixer_audio_enabled,
+                            render_frame_host);
+    media::CreateApplicationMediaInfoManager(render_frame_host,
+                                             std::move(application_session_id),
+                                             mixer_audio_enabled, std::move(r));
+    return;
+  }
 }
 
 void CastContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
diff --git a/chromecast/browser/webview/client/webview.cc b/chromecast/browser/webview/client/webview.cc
index 59e21e6..366796b 100644
--- a/chromecast/browser/webview/client/webview.cc
+++ b/chromecast/browser/webview/client/webview.cc
@@ -26,6 +26,7 @@
 
 constexpr int kGrpcMaxReconnectBackoffMs = 1000;
 
+constexpr char kBackCommand[] = "back";
 constexpr char kCreateCommand[] = "create";
 constexpr char kDestroyCommand[] = "destroy";
 constexpr char kListCommand[] = "list";
@@ -306,6 +307,8 @@
     SendResizeRequest(tokens);
   else if (tokens[1] == kPositionCommand)
     SetPosition(tokens);
+  else if (tokens[1] == kBackCommand)
+    SendBackRequest(tokens);
 
   std::cout << "Enter command: ";
   std::cout.flush();
@@ -357,6 +360,22 @@
       FROM_HERE, base::BindOnce(&WebviewClient::Paint, base::Unretained(this)));
 }
 
+void WebviewClient::SendBackRequest(const std::vector<std::string>& tokens) {
+  int id;
+  if (tokens.size() != 2 || !base::StringToInt(tokens[0], &id) ||
+      webviews_.find(id) == webviews_.end()) {
+    LOG(ERROR) << "Usage: [ID] back";
+    return;
+  }
+
+  const auto& webview = webviews_[id];
+  WebviewRequest back_request;
+  back_request.mutable_go_back();
+  if (!webview->client->Write(back_request)) {
+    LOG(ERROR) << ("Back request send failed");
+  }
+}
+
 void WebviewClient::SendNavigationRequest(
     const std::vector<std::string>& tokens) {
   int id;
diff --git a/chromecast/browser/webview/client/webview.h b/chromecast/browser/webview/client/webview.h
index a36cf4c..3a12360 100644
--- a/chromecast/browser/webview/client/webview.h
+++ b/chromecast/browser/webview/client/webview.h
@@ -81,6 +81,7 @@
   void InputCallback();
   void ListActiveWebviews();
   void Paint();
+  void SendBackRequest(const std::vector<std::string>& tokens);
   void SendNavigationRequest(const std::vector<std::string>& tokens);
   void SendResizeRequest(const std::vector<std::string>& tokens);
   void SendTouchInput(const Webview* webview,
diff --git a/chromecast/build/BUILD.gn b/chromecast/build/BUILD.gn
index b696a9f..7483874 100644
--- a/chromecast/build/BUILD.gn
+++ b/chromecast/build/BUILD.gn
@@ -15,6 +15,7 @@
 }
 
 group("archive") {
+  testonly = true
   if (chromecast_branding == "internal") {
     deps = [ "//chromecast/internal/build:archive" ]
   } else if (is_android) {
diff --git a/chromecast/media/service/cast_mojo_media_client.cc b/chromecast/media/service/cast_mojo_media_client.cc
index 101321e..9c37b478 100644
--- a/chromecast/media/service/cast_mojo_media_client.cc
+++ b/chromecast/media/service/cast_mojo_media_client.cc
@@ -35,21 +35,21 @@
 }
 
 std::unique_ptr<::media::Renderer> CastMojoMediaClient::CreateCastRenderer(
-    service_manager::mojom::InterfaceProvider* host_interfaces,
+    ::media::mojom::FrameInterfaceFactory* frame_interfaces,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
     ::media::MediaLog* /* media_log */,
     const base::UnguessableToken& overlay_plane_id) {
   DCHECK(video_geometry_setter_);
   auto cast_renderer = std::make_unique<CastRenderer>(
       backend_factory_, task_runner, video_mode_switcher_,
-      video_resolution_policy_, overlay_plane_id, host_interfaces);
+      video_resolution_policy_, overlay_plane_id, frame_interfaces);
   cast_renderer->SetVideoGeometrySetterService(video_geometry_setter_);
   return cast_renderer;
 }
 #endif
 
 std::unique_ptr<::media::Renderer> CastMojoMediaClient::CreateRenderer(
-    service_manager::mojom::InterfaceProvider* host_interfaces,
+    ::media::mojom::FrameInterfaceFactory* frame_interfaces,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
     ::media::MediaLog* /* media_log */,
     const std::string& audio_device_id) {
@@ -63,8 +63,8 @@
 }
 
 std::unique_ptr<::media::CdmFactory> CastMojoMediaClient::CreateCdmFactory(
-    service_manager::mojom::InterfaceProvider* host_interfaces) {
-  return create_cdm_factory_cb_.Run(host_interfaces);
+    ::media::mojom::FrameInterfaceFactory* frame_interfaces) {
+  return create_cdm_factory_cb_.Run(frame_interfaces);
 }
 
 }  // namespace media
diff --git a/chromecast/media/service/cast_mojo_media_client.h b/chromecast/media/service/cast_mojo_media_client.h
index 49fcada..f668fa0 100644
--- a/chromecast/media/service/cast_mojo_media_client.h
+++ b/chromecast/media/service/cast_mojo_media_client.h
@@ -24,7 +24,7 @@
  public:
   using CreateCdmFactoryCB =
       base::RepeatingCallback<std::unique_ptr<::media::CdmFactory>(
-          service_manager::mojom::InterfaceProvider*)>;
+          ::media::mojom::FrameInterfaceFactory*)>;
 
   CastMojoMediaClient(CmaBackendFactory* backend_factory,
                       const CreateCdmFactoryCB& create_cdm_factory_cb,
@@ -40,18 +40,18 @@
   // MojoMediaClient implementation:
 #if BUILDFLAG(ENABLE_CAST_RENDERER)
   std::unique_ptr<::media::Renderer> CreateCastRenderer(
-      service_manager::mojom::InterfaceProvider* host_interfaces,
+      ::media::mojom::FrameInterfaceFactory* frame_interfaces,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner,
       ::media::MediaLog* media_log,
       const base::UnguessableToken& overlay_plane_id) override;
 #endif
   std::unique_ptr<::media::Renderer> CreateRenderer(
-      service_manager::mojom::InterfaceProvider* host_interfaces,
+      ::media::mojom::FrameInterfaceFactory* frame_interfaces,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner,
       ::media::MediaLog* media_log,
       const std::string& audio_device_id) override;
   std::unique_ptr<::media::CdmFactory> CreateCdmFactory(
-      service_manager::mojom::InterfaceProvider* host_interfaces) override;
+      ::media::mojom::FrameInterfaceFactory* frame_interfaces) override;
 
  private:
   CmaBackendFactory* const backend_factory_;
diff --git a/chromecast/media/service/cast_renderer.cc b/chromecast/media/service/cast_renderer.cc
index 6865db5..4a36e52 100644
--- a/chromecast/media/service/cast_renderer.cc
+++ b/chromecast/media/service/cast_renderer.cc
@@ -60,13 +60,13 @@
     VideoModeSwitcher* video_mode_switcher,
     VideoResolutionPolicy* video_resolution_policy,
     const base::UnguessableToken& overlay_plane_id,
-    service_manager::mojom::InterfaceProvider* host_interfaces)
+    ::media::mojom::FrameInterfaceFactory* frame_interfaces)
     : backend_factory_(backend_factory),
       task_runner_(task_runner),
       video_mode_switcher_(video_mode_switcher),
       video_resolution_policy_(video_resolution_policy),
       overlay_plane_id_(overlay_plane_id),
-      host_interfaces_(host_interfaces),
+      frame_interfaces_(frame_interfaces),
       client_(nullptr),
       cast_cdm_context_(nullptr),
       media_task_runner_factory_(
@@ -79,10 +79,9 @@
   if (video_resolution_policy_)
     video_resolution_policy_->AddObserver(this);
 
-  if (host_interfaces_) {
-    host_interfaces_->GetInterface(
-        chromecast::mojom::ServiceConnector::Name_,
-        service_connector_.BindNewPipeAndPassReceiver().PassPipe());
+  if (frame_interfaces_) {
+    frame_interfaces_->BindEmbedderReceiver(mojo::GenericPendingReceiver(
+        service_connector_.BindNewPipeAndPassReceiver()));
   }
 }
 
@@ -131,11 +130,9 @@
   // Retrieve application_media_info_manager_remote_ if it is available via
   // CastApplicationMediaInfoManager.
 
-  if (host_interfaces_) {
-    host_interfaces_->GetInterface(
-        ::media::mojom::CastApplicationMediaInfoManager::Name_,
-        application_media_info_manager_remote_.BindNewPipeAndPassReceiver()
-            .PassPipe());
+  if (frame_interfaces_) {
+    frame_interfaces_->BindEmbedderReceiver(mojo::GenericPendingReceiver(
+        application_media_info_manager_remote_.BindNewPipeAndPassReceiver()));
   }
 
   if (application_media_info_manager_remote_) {
diff --git a/chromecast/media/service/cast_renderer.h b/chromecast/media/service/cast_renderer.h
index d7ad111..ba09fd5 100644
--- a/chromecast/media/service/cast_renderer.h
+++ b/chromecast/media/service/cast_renderer.h
@@ -19,6 +19,7 @@
 #include "media/base/renderer.h"
 #include "media/base/waiting.h"
 #include "media/mojo/mojom/cast_application_media_info_manager.mojom.h"
+#include "media/mojo/mojom/frame_interface_factory.mojom.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "ui/gfx/geometry/rect_f.h"
@@ -28,12 +29,6 @@
 class SingleThreadTaskRunner;
 }  // namespace base
 
-namespace service_manager {
-namespace mojom {
-class InterfaceProvider;
-}  // namespace mojom
-}  // namespace service_manager
-
 namespace chromecast {
 class TaskRunnerImpl;
 
@@ -48,13 +43,13 @@
                      public VideoResolutionPolicy::Observer,
                      public mojom::VideoGeometryChangeClient {
  public:
-  // |host_interfaces| provides interfaces tied to RenderFrameHost.
+  // |frame_interfaces| provides interfaces tied to RenderFrameHost.
   CastRenderer(CmaBackendFactory* backend_factory,
                const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
                VideoModeSwitcher* video_mode_switcher,
                VideoResolutionPolicy* video_resolution_policy,
                const base::UnguessableToken& overlay_plane_id,
-               service_manager::mojom::InterfaceProvider* host_interfaces);
+               ::media::mojom::FrameInterfaceFactory* frame_interfaces);
   ~CastRenderer() final;
   // For CmaBackend implementation, CastRenderer must be connected to
   // VideoGeometrySetterService.
@@ -120,7 +115,7 @@
   VideoResolutionPolicy* video_resolution_policy_;
   base::UnguessableToken overlay_plane_id_;
   mojo::Remote<chromecast::mojom::ServiceConnector> service_connector_;
-  service_manager::mojom::InterfaceProvider* host_interfaces_;
+  ::media::mojom::FrameInterfaceFactory* frame_interfaces_;
 
   ::media::RendererClient* client_;
   CastCdmContext* cast_cdm_context_;
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index 0216b971..15013c4 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -42,6 +42,7 @@
     ":chromeos_export",
     "//base",
     "//base:i18n",
+    "//base/util/memory_pressure",
     "//chromeos/dbus",
     "//chromeos/dbus/constants",
     "//components/policy/proto",
diff --git a/chromeos/memory/memory.cc b/chromeos/memory/memory.cc
index 894c3cf..74eca42 100644
--- a/chromeos/memory/memory.cc
+++ b/chromeos/memory/memory.cc
@@ -8,6 +8,8 @@
 #include <sys/mman.h>
 #include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
+#include "base/util/memory_pressure/system_memory_pressure_evaluator_chromeos.h"
+#include "chromeos/memory/swap_configuration.h"
 
 namespace chromeos {
 
@@ -83,4 +85,12 @@
   }
 }
 
+CHROMEOS_EXPORT void UpdateMemoryParameters() {
+  auto* monitor = util::chromeos::SystemMemoryPressureEvaluator::Get();
+  if (monitor)
+    monitor->UpdateMemoryParameters();
+
+  ConfigureSwap();
+}
+
 }  // namespace chromeos
diff --git a/chromeos/memory/memory.h b/chromeos/memory/memory.h
index a14e1a7..bc67ca0 100644
--- a/chromeos/memory/memory.h
+++ b/chromeos/memory/memory.h
@@ -30,6 +30,9 @@
 // Lock main program text segments fully.
 CHROMEOS_EXPORT void LockMainProgramText();
 
+// It should be called when some memory configuration is changed.
+CHROMEOS_EXPORT void UpdateMemoryParameters();
+
 }  // namespace chromeos
 
 #endif  // CHROMEOS_MEMORY_MEMORY_H_
diff --git a/chromeos/profiles/airmont.afdo.newest.txt b/chromeos/profiles/airmont.afdo.newest.txt
index 1ce0e89..1039024 100644
--- a/chromeos/profiles/airmont.afdo.newest.txt
+++ b/chromeos/profiles/airmont.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-airmont-83-4091.0-1585565910-benchmark-83.0.4099.3-r1-redacted.afdo.xz
\ No newline at end of file
+chromeos-chrome-amd64-airmont-83-4099.3-1586165807-benchmark-83.0.4103.7-r1-redacted.afdo.xz
\ No newline at end of file
diff --git a/chromeos/profiles/broadwell.afdo.newest.txt b/chromeos/profiles/broadwell.afdo.newest.txt
index 37bdc7a..c0617a68 100644
--- a/chromeos/profiles/broadwell.afdo.newest.txt
+++ b/chromeos/profiles/broadwell.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-broadwell-83-4085.6-1585564150-benchmark-83.0.4099.3-r1-redacted.afdo.xz
\ No newline at end of file
+chromeos-chrome-amd64-broadwell-83-4091.0-1586169176-benchmark-83.0.4103.7-r1-redacted.afdo.xz
\ No newline at end of file
diff --git a/chromeos/profiles/silvermont.afdo.newest.txt b/chromeos/profiles/silvermont.afdo.newest.txt
index 37024c5..dba6e1d 100644
--- a/chromeos/profiles/silvermont.afdo.newest.txt
+++ b/chromeos/profiles/silvermont.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-silvermont-83-4091.0-1585562740-benchmark-83.0.4099.3-r1-redacted.afdo.xz
\ No newline at end of file
+chromeos-chrome-amd64-silvermont-83-4099.3-1586168020-benchmark-83.0.4103.7-r1-redacted.afdo.xz
\ No newline at end of file
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index 89fbc87..ab9d70f 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -231,7 +231,7 @@
   deps = [
     "//ash/public/cpp",
     "//base",
-    "//base/util/memory_pressure",
+    "//chromeos",
     "//chromeos/constants",
     "//chromeos/cryptohome",
     "//chromeos/dbus:common",
diff --git a/components/arc/DEPS b/components/arc/DEPS
index dd6d083..96af6bc 100644
--- a/components/arc/DEPS
+++ b/components/arc/DEPS
@@ -3,6 +3,7 @@
   "+chromeos/constants",
   "+chromeos/cryptohome",
   "+chromeos/dbus",
+  "+chromeos/memory",
   "+chromeos/system",
   "+components/guest_os",
   "+components/account_id",
diff --git a/components/arc/session/arc_session_impl.cc b/components/arc/session/arc_session_impl.cc
index c141443..d517c0be 100644
--- a/components/arc/session/arc_session_impl.cc
+++ b/components/arc/session/arc_session_impl.cc
@@ -26,9 +26,9 @@
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
-#include "base/util/memory_pressure/system_memory_pressure_evaluator_chromeos.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/cryptohome/cryptohome_parameters.h"
+#include "chromeos/memory/memory.h"
 #include "chromeos/system/scheduler_configuration_manager_base.h"
 #include "components/arc/arc_features.h"
 #include "components/arc/arc_util.h"
@@ -575,11 +575,8 @@
   VLOG(0) << "ARC ready.";
   state_ = State::RUNNING_FULL_INSTANCE;
 
-  // Some memory parameters may be changed when ARC is launched, notify the
-  // memory monitor to update these parameters.
-  auto* monitor = util::chromeos::SystemMemoryPressureEvaluator::Get();
-  if (monitor)
-    monitor->UpdateMemoryParameters();
+  // Some memory parameters may be changed when ARC is launched.
+  chromeos::UpdateMemoryParameters();
 }
 
 void ArcSessionImpl::Stop() {
diff --git a/components/browser_watcher/fetch_system_session_events_main_win.cc b/components/browser_watcher/fetch_system_session_events_main_win.cc
index d264a6b..4a6d8b4 100644
--- a/components/browser_watcher/fetch_system_session_events_main_win.cc
+++ b/components/browser_watcher/fetch_system_session_events_main_win.cc
@@ -9,7 +9,7 @@
 #include "base/logging.h"
 #include "base/strings/stringprintf.h"
 #include "base/time/time.h"
-#include "components/metrics/system_session_analyzer_win.h"
+#include "components/metrics/system_session_analyzer/system_session_analyzer_win.h"
 
 namespace {
 
diff --git a/components/crash/core/app/dump_hung_process_with_ptype.cc b/components/crash/core/app/dump_hung_process_with_ptype.cc
index 583c16d..793c106 100644
--- a/components/crash/core/app/dump_hung_process_with_ptype.cc
+++ b/components/crash/core/app/dump_hung_process_with_ptype.cc
@@ -44,7 +44,7 @@
                            MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo;
 
   // Capture more detail for canary and dev channels. The prefix search caters
-  // for the soon to be outdated "-m" suffixed multi-install channels.
+  // for the legacy "-m" suffixed multi-install channels.
   std::string channel_name = annotations["channel"];
   if (channel_name.find("canary") == 0 || channel_name.find("dev") == 0)
     minidump_type |= MiniDumpWithIndirectlyReferencedMemory;
diff --git a/components/crash/core/app/fallback_crash_handler_win.cc b/components/crash/core/app/fallback_crash_handler_win.cc
index d26e387..a670792 100644
--- a/components/crash/core/app/fallback_crash_handler_win.cc
+++ b/components/crash/core/app/fallback_crash_handler_win.cc
@@ -145,7 +145,7 @@
                            MiniDumpWithThreadInfo;
 
   // Capture more detail for canary and dev channels. The prefix search caters
-  // for the soon to be outdated "-m" suffixed multi-install channels.
+  // for the legacy "-m" suffixed multi-install channels.
   if (channel.find("canary") == 0 || channel.find("dev") == 0)
     minidump_type |= MiniDumpWithIndirectlyReferencedMemory;
 
diff --git a/components/dbus/menu/menu_property_list.cc b/components/dbus/menu/menu_property_list.cc
index c08b367..f0ad8d3 100644
--- a/components/dbus/menu/menu_property_list.cc
+++ b/components/dbus/menu/menu_property_list.cc
@@ -9,6 +9,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/models/menu_model.h"
 #include "ui/gfx/image/image.h"
 
@@ -37,10 +38,10 @@
   if (!menu->IsVisibleAt(i))
     properties["visible"] = MakeDbusVariant(DbusBoolean(false));
 
-  gfx::Image icon;
-  if (menu->GetIconAt(i, &icon)) {
+  ui::ImageModel icon = menu->GetIconAt(i);
+  if (icon.IsImage()) {
     properties["icon-data"] =
-        MakeDbusVariant(DbusByteArray(icon.As1xPNGBytes()));
+        MakeDbusVariant(DbusByteArray(icon.GetImage().As1xPNGBytes()));
   }
 
   ui::Accelerator accelerator;
diff --git a/components/dbus/menu/menu_property_list_unittest.cc b/components/dbus/menu/menu_property_list_unittest.cc
index a5e9854..588e7a2f 100644
--- a/components/dbus/menu/menu_property_list_unittest.cc
+++ b/components/dbus/menu/menu_property_list_unittest.cc
@@ -10,6 +10,7 @@
 #include "components/dbus/properties/types.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/accelerators/accelerator.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/models/menu_model.h"
 #include "ui/base/models/menu_separator_types.h"
 #include "ui/base/models/simple_menu_model.h"
@@ -171,7 +172,8 @@
         menu->AddActionableSubMenu(0, label_, nullptr);
         break;
       case ui::MenuModel::TYPE_HIGHLIGHTED:
-        menu->AddHighlightedItemWithIcon(0, label_, icon_.AsImageSkia());
+        menu->AddHighlightedItemWithIcon(0, label_,
+                                         ui::ImageModel::FromImage(icon_));
         break;
     }
     return menu;
diff --git a/components/download/internal/common/download_response_handler.cc b/components/download/internal/common/download_response_handler.cc
index 9dd7d103..35a066f 100644
--- a/components/download/internal/common/download_response_handler.cc
+++ b/components/download/internal/common/download_response_handler.cc
@@ -84,9 +84,6 @@
   }
   if (resource_request->request_initiator.has_value())
     request_initiator_ = resource_request->request_initiator;
-  if (resource_request->trusted_params.has_value())
-    network_isolation_key_ =
-        resource_request->trusted_params->network_isolation_key;
 }
 
 DownloadResponseHandler::~DownloadResponseHandler() = default;
@@ -152,7 +149,6 @@
   create_info->request_origin = request_origin_;
   create_info->download_source = download_source_;
   create_info->request_initiator = request_initiator_;
-  create_info->network_isolation_key = network_isolation_key_;
 
   HandleResponseHeaders(head.headers.get(), create_info.get());
   return create_info;
diff --git a/components/download/public/common/download_create_info.h b/components/download/public/common/download_create_info.h
index 1fee483b..d1699c6 100644
--- a/components/download/public/common/download_create_info.h
+++ b/components/download/public/common/download_create_info.h
@@ -80,10 +80,6 @@
   // The origin of the requester that originally initiated the download.
   base::Optional<url::Origin> request_initiator;
 
-  // The key used to isolate requests from different contexts in accessing
-  // shared network resources like the cache.
-  net::NetworkIsolationKey network_isolation_key;
-
   // The time when the download started.
   base::Time start_time;
 
diff --git a/components/download/public/common/download_response_handler.h b/components/download/public/common/download_response_handler.h
index f79b29d7..1fd2fe8e9 100644
--- a/components/download/public/common/download_response_handler.h
+++ b/components/download/public/common/download_response_handler.h
@@ -99,7 +99,6 @@
   net::CertStatus cert_status_;
   bool has_strong_validators_;
   base::Optional<url::Origin> request_initiator_;
-  net::NetworkIsolationKey network_isolation_key_;
   bool is_partial_request_;
   bool completed_;
 
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
index 89a4fc78d..6347287 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
@@ -48,7 +48,7 @@
     private static final String TAG = "UrlHandler";
 
     // Enables debug logging on a local build.
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     private static final String WTAI_URL_PREFIX = "wtai://wp/";
     private static final String WTAI_MC_URL_PREFIX = "wtai://wp/mc;";
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/RedirectHandlerImpl.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/RedirectHandlerImpl.java
index d9800ee..82b7df6 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/RedirectHandlerImpl.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/RedirectHandlerImpl.java
@@ -27,7 +27,7 @@
      * An invalid entry index.
      */
     public static final int INVALID_ENTRY_INDEX = -1;
-    private static final long INVALID_TIME = -1;
+    public static final long INVALID_TIME = -1;
 
     private static final int NAVIGATION_TYPE_NONE = 0;
     private static final int NAVIGATION_TYPE_FROM_INTENT = 1;
diff --git a/components/metrics/BUILD.gn b/components/metrics/BUILD.gn
index 01487a8..7eb6049 100644
--- a/components/metrics/BUILD.gn
+++ b/components/metrics/BUILD.gn
@@ -119,8 +119,6 @@
     "stability_metrics_provider.h",
     "system_memory_stats_recorder.h",
     "system_memory_stats_recorder_linux.cc",
-    "system_session_analyzer_win.cc",
-    "system_session_analyzer_win.h",
     "ukm_demographic_metrics_provider.h",
     "unsent_log_store.cc",
     "unsent_log_store.h",
@@ -165,6 +163,10 @@
   }
 
   if (is_win) {
+    sources += [
+      "system_session_analyzer/system_session_analyzer_win.cc",
+      "system_session_analyzer/system_session_analyzer_win.h",
+    ]
     sources -= [ "machine_id_provider_nonwin.cc" ]
     deps += [ "//components/browser_watcher:stability_client" ]
     libs = [ "wevtapi.lib" ]
@@ -418,7 +420,6 @@
     "single_sample_metrics_factory_impl_unittest.cc",
     "stability_metrics_helper_unittest.cc",
     "stability_metrics_provider_unittest.cc",
-    "system_session_analyzer_win_unittest.cc",
     "ui/screen_info_metrics_provider_unittest.cc",
     "unsent_log_store_unittest.cc",
   ]
@@ -452,7 +453,10 @@
 
   if (is_win) {
     sources -= [ "machine_id_provider_nonwin_unittest.cc" ]
-    sources += [ "machine_id_provider_win_unittest.cc" ]
+    sources += [
+      "machine_id_provider_win_unittest.cc",
+      "system_session_analyzer/system_session_analyzer_win_unittest.cc",
+    ]
   }
 
   if (is_linux) {
diff --git a/components/metrics/stability_metrics_provider.cc b/components/metrics/stability_metrics_provider.cc
index 5e080db..2b6c0e6 100644
--- a/components/metrics/stability_metrics_provider.cc
+++ b/components/metrics/stability_metrics_provider.cc
@@ -18,7 +18,7 @@
 #include "base/android/build_info.h"
 #endif
 #if defined(OS_WIN)
-#include "components/metrics/system_session_analyzer_win.h"
+#include "components/metrics/system_session_analyzer/system_session_analyzer_win.h"
 #endif
 
 namespace metrics {
diff --git a/components/metrics/system_session_analyzer/OWNERS b/components/metrics/system_session_analyzer/OWNERS
new file mode 100644
index 0000000..3ffcdc56
--- /dev/null
+++ b/components/metrics/system_session_analyzer/OWNERS
@@ -0,0 +1,4 @@
+davidbienvenu@chromium.org
+jessemckenna@google.com
+
+# COMPONENT: Internals>Metrics
diff --git a/components/metrics/system_session_analyzer_win.cc b/components/metrics/system_session_analyzer/system_session_analyzer_win.cc
similarity index 98%
rename from components/metrics/system_session_analyzer_win.cc
rename to components/metrics/system_session_analyzer/system_session_analyzer_win.cc
index ca39303..600fc82 100644
--- a/components/metrics/system_session_analyzer_win.cc
+++ b/components/metrics/system_session_analyzer/system_session_analyzer_win.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/metrics/system_session_analyzer_win.h"
+#include "components/metrics/system_session_analyzer/system_session_analyzer_win.h"
 
 #include "base/stl_util.h"
 #include "base/time/time.h"
diff --git a/components/metrics/system_session_analyzer_win.h b/components/metrics/system_session_analyzer/system_session_analyzer_win.h
similarity index 93%
rename from components/metrics/system_session_analyzer_win.h
rename to components/metrics/system_session_analyzer/system_session_analyzer_win.h
index 0508185..f5d9708 100644
--- a/components/metrics/system_session_analyzer_win.h
+++ b/components/metrics/system_session_analyzer/system_session_analyzer_win.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_BROWSER_WATCHER_SYSTEM_SESSION_ANALYZER_WIN_H_
-#define COMPONENTS_BROWSER_WATCHER_SYSTEM_SESSION_ANALYZER_WIN_H_
+#ifndef COMPONENTS_METRICS_SYSTEM_SESSION_ANALYZER_SYSTEM_SESSION_ANALYZER_WIN_H_
+#define COMPONENTS_METRICS_SYSTEM_SESSION_ANALYZER_SYSTEM_SESSION_ANALYZER_WIN_H_
 
 #include <windows.h>
 #include <winevt.h>
@@ -132,4 +132,4 @@
 
 }  // namespace metrics
 
-#endif  // COMPONENTS_BROWSER_WATCHER_SYSTEM_SESSION_ANALYZER_WIN_H_
+#endif  // COMPONENTS_METRICS_SYSTEM_SESSION_ANALYZER_SYSTEM_SESSION_ANALYZER_WIN_H_
diff --git a/components/metrics/system_session_analyzer_win_unittest.cc b/components/metrics/system_session_analyzer/system_session_analyzer_win_unittest.cc
similarity index 97%
rename from components/metrics/system_session_analyzer_win_unittest.cc
rename to components/metrics/system_session_analyzer/system_session_analyzer_win_unittest.cc
index ef0a3f1..92a77c7 100644
--- a/components/metrics/system_session_analyzer_win_unittest.cc
+++ b/components/metrics/system_session_analyzer/system_session_analyzer_win_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 "components/metrics/system_session_analyzer_win.h"
+#include "components/metrics/system_session_analyzer/system_session_analyzer_win.h"
 
 #include <algorithm>
 #include <utility>
diff --git a/components/page_load_metrics/browser/metrics_web_contents_observer.cc b/components/page_load_metrics/browser/metrics_web_contents_observer.cc
index a95eacee..246b3795 100644
--- a/components/page_load_metrics/browser/metrics_web_contents_observer.cc
+++ b/components/page_load_metrics/browser/metrics_web_contents_observer.cc
@@ -560,10 +560,7 @@
 
   committed_load_ = std::move(it->second);
   back_forward_cached_pages_.erase(it);
-  // TODO(hajimehoshi): Add dedicated methods to PageLoadMetricsTracker to allow
-  // them to observe pages being restored from bfcache.
-  if (web_contents()->GetVisibility() == content::Visibility::VISIBLE)
-    committed_load_->PageShown();
+  committed_load_->OnRestoreFromBackForwardCache();
   return true;
 }
 
@@ -660,6 +657,9 @@
       kv.second->PageHidden();
     }
   }
+
+  // As pages in back-forward cache are frozen, |back_forward_cached_pages_|
+  // don't have to be iterated here.
 }
 
 // This will occur when the process for the main RenderFrameHost exits, either
diff --git a/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.cc
index 10ae9b4..eab3627 100644
--- a/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.cc
@@ -943,13 +943,23 @@
 page_load_metrics::PageLoadMetricsObserver::ObservePolicy
 CorePageLoadMetricsObserver::OnEnterBackForwardCache(
     const page_load_metrics::mojom::PageLoadTiming& timing) {
-  // TODO(hajimehoshi): Record UMA when restoring from the back-forward cache.
   UMA_HISTOGRAM_ENUMERATION(
       internal::kHistogramBackForwardCacheEvent,
       internal::PageLoadBackForwardCacheEvent::kEnterBackForwardCache);
   return PageLoadMetricsObserver::OnEnterBackForwardCache(timing);
 }
 
+void CorePageLoadMetricsObserver::OnRestoreFromBackForwardCache(
+    const page_load_metrics::mojom::PageLoadTiming& timing) {
+  // This never reaches yet because OnEnterBackForwardCache returns
+  // STOP_OBSERVING.
+  // TODO(hajimehoshi): After changing OnEnterBackForwardCache to continue
+  // observation, remove the above comment.
+  UMA_HISTOGRAM_ENUMERATION(
+      internal::kHistogramBackForwardCacheEvent,
+      internal::PageLoadBackForwardCacheEvent::kRestoreFromBackForwardCache);
+}
+
 void CorePageLoadMetricsObserver::OnLoadingBehaviorObserved(
     content::RenderFrameHost* rfh,
     int behavior_flag) {
diff --git a/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h b/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h
index 86644fd..3e9dd8c3 100644
--- a/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h
+++ b/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h
@@ -181,6 +181,8 @@
       content::NavigationHandle* navigation_handle) override;
   ObservePolicy OnEnterBackForwardCache(
       const page_load_metrics::mojom::PageLoadTiming& timing) override;
+  void OnRestoreFromBackForwardCache(
+      const page_load_metrics::mojom::PageLoadTiming& timing) override;
   void OnLoadingBehaviorObserved(content::RenderFrameHost* rfh,
                                  int behavior_flags) override;
 
diff --git a/components/page_load_metrics/browser/page_load_metrics_observer.h b/components/page_load_metrics/browser/page_load_metrics_observer.h
index b371238..2f023be 100644
--- a/components/page_load_metrics/browser/page_load_metrics_observer.h
+++ b/components/page_load_metrics/browser/page_load_metrics_observer.h
@@ -274,7 +274,7 @@
 
   // OnEnterBackForwardCache is triggered when a page is put into the
   // back-forward cache. This page can be reused in the future for a
-  // back-forward navigation, in this case this OnRestoredFromBackForwardCache
+  // back-forward navigation, in this case this OnRestoreFromBackForwardCache
   // will be called for this PageLoadMetricsObserver. Note that the page in the
   // back-forward cache can be evicted at any moment, and in this case
   // OnComplete will be called.
@@ -286,7 +286,6 @@
   // entire lifetime of the page, which is important for cases like tracking
   // feature use counts or total network usage.
   //
-  // TODO(hajimehoshi): Add OnRestoredFromBackForwardCache().
   // TODO(hajimehoshi): Consider to remove |timing| argument by adding a
   // function to PageLoadMetricsObserverDelegate. This would require
   // investigation to determine exposing the timing from the delegate would be
@@ -294,6 +293,11 @@
   virtual ObservePolicy OnEnterBackForwardCache(
       const mojom::PageLoadTiming& timing);
 
+  // OnRestoreFromBackForwardCache is triggered when a page is restored from
+  // the back-forward cache.
+  virtual void OnRestoreFromBackForwardCache(
+      const mojom::PageLoadTiming& timing) {}
+
   // Called before OnCommit. The observer should return whether it wishes to
   // observe navigations whose main resource has MIME type |mine_type|. The
   // default is to observe HTML and XHTML only. Note that PageLoadTrackers only
diff --git a/components/page_load_metrics/browser/page_load_tracker.cc b/components/page_load_metrics/browser/page_load_tracker.cc
index 38a8cbc..9e1c1eb 100644
--- a/components/page_load_metrics/browser/page_load_tracker.cc
+++ b/components/page_load_metrics/browser/page_load_tracker.cc
@@ -816,11 +816,25 @@
 }
 
 void PageLoadTracker::OnEnterBackForwardCache() {
-  // TODO(hajimehoshi): Call PageShown from OnRestoreFromBackForwardCache.
-  if (visibility_tracker_.currently_in_foreground())
+  DCHECK(visibility_tracker_.currently_in_foreground());
+  if (GetWebContents()->GetVisibility() == content::Visibility::VISIBLE) {
     PageHidden();
+  }
+
   INVOKE_AND_PRUNE_OBSERVERS(observers_, OnEnterBackForwardCache,
                              metrics_update_dispatcher_.timing());
 }
 
+void PageLoadTracker::OnRestoreFromBackForwardCache() {
+  DCHECK(!visibility_tracker_.currently_in_foreground());
+  if (GetWebContents()->GetVisibility() == content::Visibility::VISIBLE) {
+    PageShown();
+  }
+
+  for (const auto& observer : observers_) {
+    observer->OnRestoreFromBackForwardCache(
+        metrics_update_dispatcher_.timing());
+  }
+}
+
 }  // namespace page_load_metrics
diff --git a/components/page_load_metrics/browser/page_load_tracker.h b/components/page_load_metrics/browser/page_load_tracker.h
index f310d5b3..6e8ab12c 100644
--- a/components/page_load_metrics/browser/page_load_tracker.h
+++ b/components/page_load_metrics/browser/page_load_tracker.h
@@ -356,6 +356,7 @@
   void BroadcastEventToObservers(const void* const event_key);
 
   void OnEnterBackForwardCache();
+  void OnRestoreFromBackForwardCache();
 
  private:
   // This function converts a TimeTicks value taken in the browser process
diff --git a/components/payments/content/payment_app_factory.h b/components/payments/content/payment_app_factory.h
index 7d74c3a..ccd4f99 100644
--- a/components/payments/content/payment_app_factory.h
+++ b/components/payments/content/payment_app_factory.h
@@ -84,8 +84,8 @@
     // When SkipCreatingNativePaymentApps() is true, this callback is called
     // when service-worker payment app info is available.
     virtual void OnCreatingNativePaymentAppsSkipped(
-        const content::PaymentAppProvider::PaymentApps& apps,
-        const ServiceWorkerPaymentAppFinder::InstallablePaymentApps&
+        content::PaymentAppProvider::PaymentApps apps,
+        ServiceWorkerPaymentAppFinder::InstallablePaymentApps
             installable_apps) = 0;
 
     // Called when all apps of this factory have been created.
diff --git a/components/payments/content/payment_request_state.cc b/components/payments/content/payment_request_state.cc
index b2ee9bb..02a13c3 100644
--- a/components/payments/content/payment_request_state.cc
+++ b/components/payments/content/payment_request_state.cc
@@ -181,8 +181,8 @@
 }
 
 void PaymentRequestState::OnCreatingNativePaymentAppsSkipped(
-    const content::PaymentAppProvider::PaymentApps& unused_apps,
-    const ServiceWorkerPaymentAppFinder::InstallablePaymentApps&
+    content::PaymentAppProvider::PaymentApps unused_apps,
+    ServiceWorkerPaymentAppFinder::InstallablePaymentApps
         unused_installable_apps) {
   NOTREACHED();
 }
diff --git a/components/payments/content/payment_request_state.h b/components/payments/content/payment_request_state.h
index 9487b13..ce439e9e 100644
--- a/components/payments/content/payment_request_state.h
+++ b/components/payments/content/payment_request_state.h
@@ -150,9 +150,9 @@
   void OnPaymentAppCreationError(const std::string& error_message) override;
   bool SkipCreatingNativePaymentApps() const override;
   void OnCreatingNativePaymentAppsSkipped(
-      const content::PaymentAppProvider::PaymentApps& apps,
-      const ServiceWorkerPaymentAppFinder::InstallablePaymentApps&
-          installable_apps) override;
+      content::PaymentAppProvider::PaymentApps apps,
+      ServiceWorkerPaymentAppFinder::InstallablePaymentApps installable_apps)
+      override;
   void OnDoneCreatingPaymentApps() override;
 
   // PaymentResponseHelper::Delegate
diff --git a/components/performance_manager/BUILD.gn b/components/performance_manager/BUILD.gn
index 7ff448f6..fc13af0 100644
--- a/components/performance_manager/BUILD.gn
+++ b/components/performance_manager/BUILD.gn
@@ -2,6 +2,12 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//third_party/protobuf/proto_library.gni")
+
+proto_library("site_data_proto") {
+  sources = [ "persistence/site_data/site_data.proto" ]
+}
+
 static_library("performance_manager") {
   sources = [
     "decorators/page_live_state_decorator.cc",
@@ -106,6 +112,39 @@
     "//services/metrics/public/cpp:metrics_cpp",
     "//url",
   ]
+
+  if (!is_android) {
+    sources += [
+      "persistence/site_data/exponential_moving_average.cc",
+      "persistence/site_data/exponential_moving_average.h",
+      "persistence/site_data/feature_usage.h",
+      "persistence/site_data/leveldb_site_data_store.cc",
+      "persistence/site_data/leveldb_site_data_store.h",
+      "persistence/site_data/non_recording_site_data_cache.cc",
+      "persistence/site_data/non_recording_site_data_cache.h",
+      "persistence/site_data/noop_site_data_writer.cc",
+      "persistence/site_data/noop_site_data_writer.h",
+      "persistence/site_data/site_data_cache.h",
+      "persistence/site_data/site_data_cache_factory.cc",
+      "persistence/site_data/site_data_cache_factory.h",
+      "persistence/site_data/site_data_cache_impl.cc",
+      "persistence/site_data/site_data_cache_impl.h",
+      "persistence/site_data/site_data_cache_inspector.h",
+      "persistence/site_data/site_data_impl.cc",
+      "persistence/site_data/site_data_impl.h",
+      "persistence/site_data/site_data_reader.cc",
+      "persistence/site_data/site_data_reader.h",
+      "persistence/site_data/site_data_store.h",
+      "persistence/site_data/site_data_writer.cc",
+      "persistence/site_data/site_data_writer.h",
+      "persistence/site_data/tab_visibility.h",
+    ]
+
+    public_deps += [
+      ":site_data_proto",
+      "//third_party/leveldatabase",
+    ]
+  }
 }
 
 source_set("unit_tests") {
@@ -150,4 +189,20 @@
     "//testing/gmock",
     "//testing/gtest",
   ]
+
+  # The site data database isn't supported on Android.
+  if (!is_android) {
+    sources += [
+      "persistence/site_data/exponential_moving_average_unittest.cc",
+      "persistence/site_data/leveldb_site_data_store_unittest.cc",
+      "persistence/site_data/non_recording_site_data_cache_unittest.cc",
+      "persistence/site_data/site_data_cache_factory_unittest.cc",
+      "persistence/site_data/site_data_cache_impl_unittest.cc",
+      "persistence/site_data/site_data_impl_unittest.cc",
+      "persistence/site_data/site_data_reader_unittest.cc",
+      "persistence/site_data/site_data_writer_unittest.cc",
+      "persistence/site_data/unittest_utils.cc",
+      "persistence/site_data/unittest_utils.h",
+    ]
+  }
 }
diff --git a/components/performance_manager/DEPS b/components/performance_manager/DEPS
index e31899f..5447926 100644
--- a/components/performance_manager/DEPS
+++ b/components/performance_manager/DEPS
@@ -5,4 +5,5 @@
   "+mojo/public",
   "+services/metrics/public/cpp",
   "+third_party/blink/public/mojom/favicon",
+  "+third_party/leveldatabase",
 ]
diff --git a/components/performance_manager/decorators/page_load_tracker_decorator.cc b/components/performance_manager/decorators/page_load_tracker_decorator.cc
index 909bd43..bf96085 100644
--- a/components/performance_manager/decorators/page_load_tracker_decorator.cc
+++ b/components/performance_manager/decorators/page_load_tracker_decorator.cc
@@ -11,6 +11,7 @@
 #include "components/performance_manager/graph/node_attached_data_impl.h"
 #include "components/performance_manager/graph/page_node_impl.h"
 #include "components/performance_manager/graph/process_node_impl.h"
+#include "components/performance_manager/public/graph/node_data_describer_registry.h"
 
 namespace performance_manager {
 
@@ -45,6 +46,24 @@
   DISALLOW_COPY_AND_ASSIGN(DataImpl);
 };
 
+// static
+const char* ToString(LoadIdleState state) {
+  switch (state) {
+    case LoadIdleState::kLoadingNotStarted:
+      return "kLoadingNotStarted";
+    case LoadIdleState::kLoading:
+      return "kLoading";
+    case LoadIdleState::kLoadedNotIdling:
+      return "kLoadedNotIdling";
+    case LoadIdleState::kLoadedAndIdling:
+      return "kLoadedAndIdling";
+    case LoadIdleState::kLoadedAndIdle:
+      return "kLoadedAndIdle";
+  }
+}
+
+const char kDescriberName[] = "PageLoadTrackerDecorator";
+
 }  // namespace
 
 // static
@@ -67,12 +86,30 @@
 
 void PageLoadTrackerDecorator::OnPassedToGraph(Graph* graph) {
   RegisterObservers(graph);
+  graph->GetNodeDataDescriberRegistry()->RegisterDescriber(this,
+                                                           kDescriberName);
 }
 
 void PageLoadTrackerDecorator::OnTakenFromGraph(Graph* graph) {
+  graph->GetNodeDataDescriberRegistry()->UnregisterDescriber(this);
   UnregisterObservers(graph);
 }
 
+base::Value PageLoadTrackerDecorator::DescribePageNodeData(
+    const PageNode* page_node) const {
+  auto* data = DataImpl::Get(PageNodeImpl::FromNode(page_node));
+  if (data == nullptr)
+    return base::Value();
+
+  base::Value ret(base::Value::Type::DICTIONARY);
+  ret.SetKey("load_idle_state_",
+             base::Value(ToString(data->load_idle_state())));
+  ret.SetKey("loading_received_response_",
+             base::Value(data->loading_received_response_));
+
+  return ret;
+}
+
 void PageLoadTrackerDecorator::OnMainThreadTaskLoadIsLow(
     const ProcessNode* process_node) {
   UpdateLoadIdleStateProcess(ProcessNodeImpl::FromNode(process_node));
diff --git a/components/performance_manager/decorators/page_load_tracker_decorator.h b/components/performance_manager/decorators/page_load_tracker_decorator.h
index f0a8511..e924a2a 100644
--- a/components/performance_manager/decorators/page_load_tracker_decorator.h
+++ b/components/performance_manager/decorators/page_load_tracker_decorator.h
@@ -9,6 +9,7 @@
 #include "base/timer/timer.h"
 #include "components/performance_manager/public/graph/frame_node.h"
 #include "components/performance_manager/public/graph/graph.h"
+#include "components/performance_manager/public/graph/node_data_describer.h"
 #include "components/performance_manager/public/graph/page_node.h"
 #include "components/performance_manager/public/graph/process_node.h"
 
@@ -25,6 +26,7 @@
 // absolute timeout. This state is then updated on PageNodes in a graph.
 class PageLoadTrackerDecorator : public FrameNode::ObserverDefaultImpl,
                                  public GraphOwnedDefaultImpl,
+                                 public NodeDataDescriberDefaultImpl,
                                  public ProcessNode::ObserverDefaultImpl {
  public:
   class Data;
@@ -39,6 +41,9 @@
   void OnPassedToGraph(Graph* graph) override;
   void OnTakenFromGraph(Graph* graph) override;
 
+  // NodeDataDescriber implementation:
+  base::Value DescribePageNodeData(const PageNode* node) const override;
+
   // ProcessNodeObserver implementation:
   void OnMainThreadTaskLoadIsLow(const ProcessNode* process_node) override;
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/exponential_moving_average.cc b/components/performance_manager/persistence/site_data/exponential_moving_average.cc
similarity index 92%
rename from chrome/browser/performance_manager/persistence/site_data/exponential_moving_average.cc
rename to components/performance_manager/persistence/site_data/exponential_moving_average.cc
index 82b6e24b..d7bdb5c 100644
--- a/chrome/browser/performance_manager/persistence/site_data/exponential_moving_average.cc
+++ b/components/performance_manager/persistence/site_data/exponential_moving_average.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/performance_manager/persistence/site_data/exponential_moving_average.h"
+#include "components/performance_manager/persistence/site_data/exponential_moving_average.h"
 
 #include <cmath>
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/exponential_moving_average.h b/components/performance_manager/persistence/site_data/exponential_moving_average.h
similarity index 82%
rename from chrome/browser/performance_manager/persistence/site_data/exponential_moving_average.h
rename to components/performance_manager/persistence/site_data/exponential_moving_average.h
index 5b7b62f3..8dc7ed25 100644
--- a/chrome/browser/performance_manager/persistence/site_data/exponential_moving_average.h
+++ b/components/performance_manager/persistence/site_data/exponential_moving_average.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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_EXPONENTIAL_MOVING_AVERAGE_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_EXPONENTIAL_MOVING_AVERAGE_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_EXPONENTIAL_MOVING_AVERAGE_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_EXPONENTIAL_MOVING_AVERAGE_H_
 
 #include <cstddef>
 
@@ -48,4 +48,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_EXPONENTIAL_MOVING_AVERAGE_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_EXPONENTIAL_MOVING_AVERAGE_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/exponential_moving_average_unittest.cc b/components/performance_manager/persistence/site_data/exponential_moving_average_unittest.cc
similarity index 95%
rename from chrome/browser/performance_manager/persistence/site_data/exponential_moving_average_unittest.cc
rename to components/performance_manager/persistence/site_data/exponential_moving_average_unittest.cc
index b8419b68..14fdfc6 100644
--- a/chrome/browser/performance_manager/persistence/site_data/exponential_moving_average_unittest.cc
+++ b/components/performance_manager/persistence/site_data/exponential_moving_average_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/performance_manager/persistence/site_data/exponential_moving_average.h"
+#include "components/performance_manager/persistence/site_data/exponential_moving_average.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/feature_usage.h b/components/performance_manager/persistence/site_data/feature_usage.h
similarity index 65%
rename from chrome/browser/performance_manager/persistence/site_data/feature_usage.h
rename to components/performance_manager/persistence/site_data/feature_usage.h
index 2496abb8..a8db38f 100644
--- a/chrome/browser/performance_manager/persistence/site_data/feature_usage.h
+++ b/components/performance_manager/persistence/site_data/feature_usage.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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_FEATURE_USAGE_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_FEATURE_USAGE_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_FEATURE_USAGE_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_FEATURE_USAGE_H_
 
 namespace performance_manager {
 
@@ -17,4 +17,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_FEATURE_USAGE_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_FEATURE_USAGE_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.cc b/components/performance_manager/persistence/site_data/leveldb_site_data_store.cc
similarity index 99%
rename from chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.cc
rename to components/performance_manager/persistence/site_data/leveldb_site_data_store.cc
index 86700dd..fdce3764 100644
--- a/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.cc
+++ b/components/performance_manager/persistence/site_data/leveldb_site_data_store.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/performance_manager/persistence/site_data/leveldb_site_data_store.h"
+#include "components/performance_manager/persistence/site_data/leveldb_site_data_store.h"
 
 #include <limits>
 #include <string>
diff --git a/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.h b/components/performance_manager/persistence/site_data/leveldb_site_data_store.h
similarity index 88%
rename from chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.h
rename to components/performance_manager/persistence/site_data/leveldb_site_data_store.h
index fb84041..e7ec966 100644
--- a/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.h
+++ b/components/performance_manager/persistence/site_data/leveldb_site_data_store.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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_LEVELDB_SITE_DATA_STORE_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_LEVELDB_SITE_DATA_STORE_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_LEVELDB_SITE_DATA_STORE_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_LEVELDB_SITE_DATA_STORE_H_
 
 #include "base/auto_reset.h"
 #include "base/files/file_path.h"
@@ -11,7 +11,7 @@
 #include "base/sequence_checker.h"
 #include "base/sequenced_task_runner.h"
 #include "base/task/post_task.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_store.h"
+#include "components/performance_manager/persistence/site_data/site_data_store.h"
 #include "third_party/leveldatabase/src/include/leveldb/db.h"
 
 namespace performance_manager {
@@ -79,4 +79,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_LEVELDB_SITE_DATA_STORE_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_LEVELDB_SITE_DATA_STORE_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store_unittest.cc b/components/performance_manager/persistence/site_data/leveldb_site_data_store_unittest.cc
similarity index 98%
rename from chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store_unittest.cc
rename to components/performance_manager/persistence/site_data/leveldb_site_data_store_unittest.cc
index 5058341..cd91a8d 100644
--- a/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store_unittest.cc
+++ b/components/performance_manager/persistence/site_data/leveldb_site_data_store_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/performance_manager/persistence/site_data/leveldb_site_data_store.h"
+#include "components/performance_manager/persistence/site_data/leveldb_site_data_store.h"
 
 #include <limits>
 
@@ -16,7 +16,7 @@
 #include "base/test/task_environment.h"
 #include "base/test/test_file_util.h"
 #include "build/build_config.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data.pb.h"
+#include "components/performance_manager/persistence/site_data/site_data.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/leveldatabase/leveldb_chrome.h"
 #include "url/gurl.h"
diff --git a/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.cc b/components/performance_manager/persistence/site_data/non_recording_site_data_cache.cc
similarity index 84%
rename from chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.cc
rename to components/performance_manager/persistence/site_data/non_recording_site_data_cache.cc
index e9e0ec5..a2072840 100644
--- a/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.cc
+++ b/components/performance_manager/persistence/site_data/non_recording_site_data_cache.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.h"
+#include "components/performance_manager/persistence/site_data/non_recording_site_data_cache.h"
 
 #include "base/memory/ptr_util.h"
-#include "chrome/browser/performance_manager/persistence/site_data/noop_site_data_writer.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_reader.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_writer.h"
+#include "components/performance_manager/persistence/site_data/noop_site_data_writer.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_factory.h"
+#include "components/performance_manager/persistence/site_data/site_data_reader.h"
+#include "components/performance_manager/persistence/site_data/site_data_writer.h"
 
 namespace performance_manager {
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.h b/components/performance_manager/persistence/site_data/non_recording_site_data_cache.h
similarity index 81%
rename from chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.h
rename to components/performance_manager/persistence/site_data/non_recording_site_data_cache.h
index b7d7ddd..72295ec505 100644
--- a/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.h
+++ b/components/performance_manager/persistence/site_data/non_recording_site_data_cache.h
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NON_RECORDING_SITE_DATA_CACHE_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NON_RECORDING_SITE_DATA_CACHE_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NON_RECORDING_SITE_DATA_CACHE_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NON_RECORDING_SITE_DATA_CACHE_H_
 
 #include <memory>
 #include <string>
 #include <vector>
 
 #include "base/macros.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_inspector.h"
 
 namespace performance_manager {
 
@@ -60,4 +60,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NON_RECORDING_SITE_DATA_CACHE_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NON_RECORDING_SITE_DATA_CACHE_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache_unittest.cc b/components/performance_manager/persistence/site_data/non_recording_site_data_cache_unittest.cc
similarity index 77%
rename from chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache_unittest.cc
rename to components/performance_manager/persistence/site_data/non_recording_site_data_cache_unittest.cc
index 81e9ccf..baf35a6 100644
--- a/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache_unittest.cc
+++ b/components/performance_manager/persistence/site_data/non_recording_site_data_cache_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/performance_manager/persistence/site_data/non_recording_site_data_cache.h"
+#include "components/performance_manager/persistence/site_data/non_recording_site_data_cache.h"
 
 #include "base/memory/ptr_util.h"
-#include "chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h"
-#include "chrome/test/base/testing_profile.h"
+#include "components/performance_manager/persistence/site_data/leveldb_site_data_store.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_factory.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_inspector.h"
 #include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_browser_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
@@ -24,14 +24,15 @@
   NonRecordingSiteDataCacheTest()
       : use_in_memory_db_for_testing_(
             LevelDBSiteDataStore::UseInMemoryDBForTesting()),
-        factory_(std::make_unique<SiteDataCacheFactory>()),
-        off_the_record_profile_(parent_profile_.GetOffTheRecordProfile()) {}
+        factory_(std::make_unique<SiteDataCacheFactory>()) {
+    off_the_record_browser_context_.set_is_off_the_record(true);
+  }
 
   ~NonRecordingSiteDataCacheTest() override = default;
 
   void SetUp() override {
     recording_data_cache_ = base::WrapUnique(new SiteDataCacheImpl(
-        parent_profile_.UniqueId(), parent_profile_.GetPath()));
+        parent_browser_context_.UniqueId(), parent_browser_context_.GetPath()));
 
     // Wait for the database to be initialized.
     base::RunLoop run_loop;
@@ -40,7 +41,7 @@
     run_loop.Run();
 
     non_recording_data_cache_ = std::make_unique<NonRecordingSiteDataCache>(
-        off_the_record_profile_->UniqueId(), recording_data_cache_.get(),
+        off_the_record_browser_context_.UniqueId(), recording_data_cache_.get(),
         recording_data_cache_.get());
   }
 
@@ -51,17 +52,16 @@
   content::BrowserTaskEnvironment task_environment_;
 
   // Ensure that the database used by the data store owned by
-  // |recording_data_cache_| gets created in memory. This avoid having to wait
-  // for it to be fully closed before destroying |parent_profile_|.
+  // |recording_data_cache_| gets created in memory.
   std::unique_ptr<base::AutoReset<bool>> use_in_memory_db_for_testing_;
 
   // The data cache factory that will be used by the caches tested here.
   std::unique_ptr<SiteDataCacheFactory> factory_;
 
-  // The on the record profile.
-  TestingProfile parent_profile_;
-  // An off the record profile owned by |parent_profile|.
-  Profile* off_the_record_profile_;
+  // The on the record browser context.
+  content::TestBrowserContext parent_browser_context_;
+  // An off the record context.
+  content::TestBrowserContext off_the_record_browser_context_;
 
   std::unique_ptr<SiteDataCacheImpl> recording_data_cache_;
   std::unique_ptr<NonRecordingSiteDataCache> non_recording_data_cache_;
@@ -104,7 +104,7 @@
 TEST_F(NonRecordingSiteDataCacheTest, InspectorWorks) {
   // Make sure the inspector interface was registered at construction.
   SiteDataCacheInspector* inspector = factory_->GetInspectorForBrowserContext(
-      off_the_record_profile_->UniqueId());
+      off_the_record_browser_context_.UniqueId());
   EXPECT_NE(nullptr, inspector);
   EXPECT_EQ(non_recording_data_cache_.get(), inspector);
 
@@ -138,7 +138,7 @@
   // destruction.
   non_recording_data_cache_.reset();
   EXPECT_EQ(nullptr, factory_->GetInspectorForBrowserContext(
-                         off_the_record_profile_->UniqueId()));
+                         off_the_record_browser_context_.UniqueId()));
 }
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/noop_site_data_writer.cc b/components/performance_manager/persistence/site_data/noop_site_data_writer.cc
similarity index 91%
rename from chrome/browser/performance_manager/persistence/site_data/noop_site_data_writer.cc
rename to components/performance_manager/persistence/site_data/noop_site_data_writer.cc
index 3d1738d9..5e7824f 100644
--- a/chrome/browser/performance_manager/persistence/site_data/noop_site_data_writer.cc
+++ b/components/performance_manager/persistence/site_data/noop_site_data_writer.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/performance_manager/persistence/site_data/noop_site_data_writer.h"
+#include "components/performance_manager/persistence/site_data/noop_site_data_writer.h"
 
 namespace performance_manager {
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/noop_site_data_writer.h b/components/performance_manager/persistence/site_data/noop_site_data_writer.h
similarity index 76%
rename from chrome/browser/performance_manager/persistence/site_data/noop_site_data_writer.h
rename to components/performance_manager/persistence/site_data/noop_site_data_writer.h
index 94a17cf..5fc8837 100644
--- a/chrome/browser/performance_manager/persistence/site_data/noop_site_data_writer.h
+++ b/components/performance_manager/persistence/site_data/noop_site_data_writer.h
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NOOP_SITE_DATA_WRITER_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NOOP_SITE_DATA_WRITER_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NOOP_SITE_DATA_WRITER_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NOOP_SITE_DATA_WRITER_H_
 
 #include "base/macros.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_writer.h"
+#include "components/performance_manager/persistence/site_data/site_data_writer.h"
 
 namespace performance_manager {
 
@@ -39,4 +39,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NOOP_SITE_DATA_WRITER_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NOOP_SITE_DATA_WRITER_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data.proto b/components/performance_manager/persistence/site_data/site_data.proto
similarity index 100%
rename from chrome/browser/performance_manager/persistence/site_data/site_data.proto
rename to components/performance_manager/persistence/site_data/site_data.proto
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache.h b/components/performance_manager/persistence/site_data/site_data_cache.h
similarity index 70%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_cache.h
rename to components/performance_manager/persistence/site_data/site_data_cache.h
index 1776e711..f37d099 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache.h
+++ b/components/performance_manager/persistence/site_data/site_data_cache.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_H_
 
 #include <memory>
 
 #include "base/macros.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_reader.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_writer.h"
-#include "chrome/browser/performance_manager/persistence/site_data/tab_visibility.h"
+#include "components/performance_manager/persistence/site_data/site_data_reader.h"
+#include "components/performance_manager/persistence/site_data/site_data_writer.h"
+#include "components/performance_manager/persistence/site_data/tab_visibility.h"
 #include "url/origin.h"
 
 namespace performance_manager {
@@ -44,4 +44,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.cc b/components/performance_manager/persistence/site_data/site_data_cache_factory.cc
similarity index 93%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.cc
rename to components/performance_manager/persistence/site_data/site_data_cache_factory.cc
index f57bf91..6789c9b 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.cc
+++ b/components/performance_manager/persistence/site_data/site_data_cache_factory.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_factory.h"
 
 #include <utility>
 
@@ -10,10 +10,10 @@
 #include "base/sequenced_task_runner.h"
 #include "base/stl_util.h"
 #include "base/task_runner_util.h"
-#include "chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h"
 #include "components/performance_manager/performance_manager_impl.h"
+#include "components/performance_manager/persistence/site_data/non_recording_site_data_cache.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_inspector.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h b/components/performance_manager/persistence/site_data/site_data_cache_factory.h
similarity index 92%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h
rename to components/performance_manager/persistence/site_data/site_data_cache_factory.h
index 9880c51..2173d53 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h
+++ b/components/performance_manager/persistence/site_data/site_data_cache_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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_FACTORY_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_FACTORY_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_FACTORY_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_FACTORY_H_
 
 #include <memory>
 #include <string>
@@ -16,7 +16,7 @@
 #include "base/optional.h"
 #include "base/sequence_checker.h"
 #include "base/sequenced_task_runner.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache.h"
 #include "components/performance_manager/public/graph/graph.h"
 #include "content/public/browser/browser_context.h"
 
@@ -122,4 +122,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_FACTORY_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_FACTORY_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc b/components/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc
similarity index 73%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc
rename to components/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc
index 2497e2ff..4383f021 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc
+++ b/components/performance_manager/persistence/site_data/site_data_cache_factory_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/performance_manager/persistence/site_data/site_data_cache_factory.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_factory.h"
 
 #include <memory>
 #include <utility>
@@ -13,16 +13,16 @@
 #include "base/sequenced_task_runner.h"
 #include "base/task/post_task.h"
 #include "base/test/bind_test_util.h"
-#include "chrome/browser/performance_manager/persistence/site_data/unittest_utils.h"
-#include "chrome/test/base/testing_profile.h"
+#include "components/performance_manager/performance_manager_impl.h"
 #include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_browser_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace performance_manager {
 
-using SiteDataCacheFactoryTest = testing::TestWithPerformanceManager;
-
-TEST_F(SiteDataCacheFactoryTest, EndToEnd) {
+TEST(SiteDataCacheFactoryTest, EndToEnd) {
+  content::BrowserTaskEnvironment task_environment;
+  auto performance_manager = PerformanceManagerImpl::Create(base::DoNothing());
   std::unique_ptr<SiteDataCacheFactory> factory =
       std::make_unique<SiteDataCacheFactory>();
   SiteDataCacheFactory* factory_raw = factory.get();
@@ -35,9 +35,9 @@
           },
           std::move(factory)));
 
-  TestingProfile profile;
-  SiteDataCacheFactory::OnBrowserContextCreatedOnUIThread(factory_raw, &profile,
-                                                          nullptr);
+  content::TestBrowserContext browser_context;
+  SiteDataCacheFactory::OnBrowserContextCreatedOnUIThread(
+      factory_raw, &browser_context, nullptr);
 
   {
     base::RunLoop run_loop;
@@ -53,13 +53,13 @@
                                      browser_context_id));
               std::move(quit_closure).Run();
             },
-            base::Unretained(factory_raw), profile.UniqueId(),
+            base::Unretained(factory_raw), browser_context.UniqueId(),
             run_loop.QuitClosure()));
     run_loop.Run();
   }
 
   SiteDataCacheFactory::OnBrowserContextDestroyedOnUIThread(factory_raw,
-                                                            &profile);
+                                                            &browser_context);
   {
     base::RunLoop run_loop;
     PerformanceManagerImpl::CallOnGraphImpl(
@@ -74,10 +74,13 @@
                                      browser_context_id));
               std::move(quit_closure).Run();
             },
-            base::Unretained(factory_raw), profile.UniqueId(),
+            base::Unretained(factory_raw), browser_context.UniqueId(),
             run_loop.QuitClosure()));
     run_loop.Run();
   }
+
+  PerformanceManagerImpl::Destroy(std::move(performance_manager));
+  task_environment.RunUntilIdle();
 }
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.cc b/components/performance_manager/persistence/site_data/site_data_cache_impl.cc
similarity index 92%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.cc
rename to components/performance_manager/persistence/site_data/site_data_cache_impl.cc
index 0f48ee0..fafd303 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.cc
+++ b/components/performance_manager/persistence/site_data/site_data_cache_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/performance_manager/persistence/site_data/site_data_cache_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_impl.h"
 
 #include <set>
 
@@ -10,10 +10,10 @@
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
 #include "base/stl_util.h"
-#include "chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_reader.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_writer.h"
+#include "components/performance_manager/persistence/site_data/leveldb_site_data_store.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_factory.h"
+#include "components/performance_manager/persistence/site_data/site_data_reader.h"
+#include "components/performance_manager/persistence/site_data/site_data_writer.h"
 
 namespace performance_manager {
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h b/components/performance_manager/persistence/site_data/site_data_cache_impl.h
similarity index 86%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h
rename to components/performance_manager/persistence/site_data/site_data_cache_impl.h
index 5194e3c..587ce99 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h
+++ b/components/performance_manager/persistence/site_data/site_data_cache_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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_IMPL_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_IMPL_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_IMPL_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_IMPL_H_
 
 #include <memory>
 #include <string>
@@ -17,9 +17,9 @@
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "base/sequence_checker.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_inspector.h"
+#include "components/performance_manager/persistence/site_data/site_data_impl.h"
 
 namespace performance_manager {
 
@@ -105,4 +105,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_IMPL_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_IMPL_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl_unittest.cc b/components/performance_manager/persistence/site_data/site_data_cache_impl_unittest.cc
similarity index 91%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl_unittest.cc
rename to components/performance_manager/persistence/site_data/site_data_cache_impl_unittest.cc
index 0ba80494..0528480a 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl_unittest.cc
+++ b/components/performance_manager/persistence/site_data/site_data_cache_impl_unittest.cc
@@ -2,19 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_impl.h"
 
 #include <set>
 
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/test/scoped_feature_list.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
-#include "chrome/browser/performance_manager/persistence/site_data/unittest_utils.h"
-#include "chrome/test/base/testing_profile.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_factory.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_inspector.h"
+#include "components/performance_manager/persistence/site_data/site_data_impl.h"
+#include "components/performance_manager/persistence/site_data/unittest_utils.h"
 #include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_browser_context.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -52,8 +52,8 @@
  protected:
   SiteDataCacheImplTest()
       : data_cache_factory_(std::make_unique<SiteDataCacheFactory>()) {
-    data_cache_ = std::make_unique<SiteDataCacheImpl>(profile_.UniqueId(),
-                                                      profile_.GetPath());
+    data_cache_ = std::make_unique<SiteDataCacheImpl>(
+        browser_context_.UniqueId(), browser_context_.GetPath());
     mock_db_ = new ::testing::StrictMock<MockSiteCache>();
     data_cache_->SetDataStoreForTesting(base::WrapUnique(mock_db_));
     WaitForAsyncOperationsToComplete();
@@ -122,7 +122,7 @@
   content::BrowserTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   base::test::ScopedFeatureList scoped_feature_list_;
-  TestingProfile profile_;
+  content::TestBrowserContext browser_context_;
 
   // Owned by |data_cache_|.
   ::testing::StrictMock<MockSiteCache>* mock_db_ = nullptr;
@@ -234,7 +234,7 @@
   // Make sure the inspector interface was registered at construction.
   SiteDataCacheInspector* inspector =
       SiteDataCacheFactory::GetInstance()->GetInspectorForBrowserContext(
-          profile_.UniqueId());
+          browser_context_.UniqueId());
   EXPECT_NE(nullptr, inspector);
   EXPECT_EQ(data_cache_.get(), inspector);
 
@@ -264,11 +264,12 @@
     EXPECT_TRUE(is_dirty);
   }
 
-  // Make sure the interface is unregistered from the profile on destruction.
+  // Make sure the interface is unregistered from the browser context on
+  // destruction.
   data_cache_.reset();
   EXPECT_EQ(nullptr,
             SiteDataCacheFactory::GetInstance()->GetInspectorForBrowserContext(
-                profile_.UniqueId()));
+                browser_context_.UniqueId()));
 }
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h b/components/performance_manager/persistence/site_data/site_data_cache_inspector.h
similarity index 85%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h
rename to components/performance_manager/persistence/site_data/site_data_cache_inspector.h
index 7bfc106..84d5207 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h
+++ b/components/performance_manager/persistence/site_data/site_data_cache_inspector.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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_INSPECTOR_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_INSPECTOR_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_INSPECTOR_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_INSPECTOR_H_
 
 #include <cstdint>
 #include <memory>
@@ -13,7 +13,7 @@
 #include "base/callback.h"
 #include "base/optional.h"
 #include "base/supports_user_data.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data.pb.h"
+#include "components/performance_manager/persistence/site_data/site_data.pb.h"
 #include "url/origin.h"
 
 namespace performance_manager {
@@ -57,4 +57,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_INSPECTOR_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_CACHE_INSPECTOR_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_impl.cc b/components/performance_manager/persistence/site_data/site_data_impl.cc
similarity index 99%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_impl.cc
rename to components/performance_manager/persistence/site_data/site_data_impl.cc
index 22f99c2..16e50ef3 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_impl.cc
+++ b/components/performance_manager/persistence/site_data/site_data_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/performance_manager/persistence/site_data/site_data_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_impl.h"
 
 #include <algorithm>
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_impl.h b/components/performance_manager/persistence/site_data/site_data_impl.h
similarity index 93%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_impl.h
rename to components/performance_manager/persistence/site_data/site_data_impl.h
index bca3c608..55620e1 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_impl.h
+++ b/components/performance_manager/persistence/site_data/site_data_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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_IMPL_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_IMPL_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_IMPL_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_IMPL_H_
 
 #include <utility>
 #include <vector>
@@ -15,11 +15,11 @@
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
-#include "chrome/browser/performance_manager/persistence/site_data/exponential_moving_average.h"
-#include "chrome/browser/performance_manager/persistence/site_data/feature_usage.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data.pb.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_store.h"
-#include "chrome/browser/performance_manager/persistence/site_data/tab_visibility.h"
+#include "components/performance_manager/persistence/site_data/exponential_moving_average.h"
+#include "components/performance_manager/persistence/site_data/feature_usage.h"
+#include "components/performance_manager/persistence/site_data/site_data.pb.h"
+#include "components/performance_manager/persistence/site_data/site_data_store.h"
+#include "components/performance_manager/persistence/site_data/tab_visibility.h"
 #include "url/origin.h"
 
 namespace performance_manager {
@@ -286,4 +286,4 @@
 }  // namespace internal
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_IMPL_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_IMPL_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc b/components/performance_manager/persistence/site_data/site_data_impl_unittest.cc
similarity index 99%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc
rename to components/performance_manager/persistence/site_data/site_data_impl_unittest.cc
index db601d2f8..f762bf27 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc
+++ b/components/performance_manager/persistence/site_data/site_data_impl_unittest.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/performance_manager/persistence/site_data/site_data_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_impl.h"
 
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/task_environment.h"
-#include "chrome/browser/performance_manager/persistence/site_data/unittest_utils.h"
+#include "components/performance_manager/persistence/site_data/unittest_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_reader.cc b/components/performance_manager/persistence/site_data/site_data_reader.cc
similarity index 89%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_reader.cc
rename to components/performance_manager/persistence/site_data/site_data_reader.cc
index 34b525ff9..197a490 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_reader.cc
+++ b/components/performance_manager/persistence/site_data/site_data_reader.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_reader.h"
+#include "components/performance_manager/persistence/site_data/site_data_reader.h"
 
 #include <utility>
 
 #include "base/bind.h"
 #include "base/callback.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_impl.h"
 
 namespace performance_manager {
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_reader.h b/components/performance_manager/persistence/site_data/site_data_reader.h
similarity index 88%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_reader.h
rename to components/performance_manager/persistence/site_data/site_data_reader.h
index 61c811a1..193d02d 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_reader.h
+++ b/components/performance_manager/persistence/site_data/site_data_reader.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_READER_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_READER_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_READER_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_READER_H_
 
 #include "base/callback_forward.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/performance_manager/persistence/site_data/feature_usage.h"
+#include "components/performance_manager/persistence/site_data/feature_usage.h"
 
 namespace performance_manager {
 
@@ -73,4 +73,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_READER_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_READER_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc b/components/performance_manager/persistence/site_data/site_data_reader_unittest.cc
similarity index 96%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc
rename to components/performance_manager/persistence/site_data/site_data_reader_unittest.cc
index fae2053d..3f43575 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc
+++ b/components/performance_manager/persistence/site_data/site_data_reader_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/performance_manager/persistence/site_data/site_data_reader.h"
+#include "components/performance_manager/persistence/site_data/site_data_reader.h"
 
 #include <memory>
 #include <utility>
@@ -13,8 +13,8 @@
 #include "base/memory/ptr_util.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/task_environment.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
-#include "chrome/browser/performance_manager/persistence/site_data/unittest_utils.h"
+#include "components/performance_manager/persistence/site_data/site_data_impl.h"
+#include "components/performance_manager/persistence/site_data/unittest_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
@@ -71,7 +71,6 @@
   // SiteDataImpl is protected and not visible to
   // base::MakeRefCounted.
   SiteDataReaderTest() {
-
     test_impl_ = base::WrapRefCounted(new internal::SiteDataImpl(
         url::Origin::Create(GURL("foo.com")), &delegate_, &data_store_));
     test_impl_->NotifySiteLoaded();
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_store.h b/components/performance_manager/persistence/site_data/site_data_store.h
similarity index 85%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_store.h
rename to components/performance_manager/persistence/site_data/site_data_store.h
index 54d64d7..9301661 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_store.h
+++ b/components/performance_manager/persistence/site_data/site_data_store.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_STORE_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_STORE_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_STORE_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_STORE_H_
 
 #include <vector>
 
 #include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/optional.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data.pb.h"
+#include "components/performance_manager/persistence/site_data/site_data.pb.h"
 #include "url/origin.h"
 
 class SiteDataProto;
@@ -61,4 +61,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_STORE_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_STORE_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_writer.cc b/components/performance_manager/persistence/site_data/site_data_writer.cc
similarity index 96%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_writer.cc
rename to components/performance_manager/persistence/site_data/site_data_writer.cc
index 50becf76..b3c021fc 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_writer.cc
+++ b/components/performance_manager/persistence/site_data/site_data_writer.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/performance_manager/persistence/site_data/site_data_writer.h"
+#include "components/performance_manager/persistence/site_data/site_data_writer.h"
 
 #include <utility>
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_writer.h b/components/performance_manager/persistence/site_data/site_data_writer.h
similarity index 84%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_writer.h
rename to components/performance_manager/persistence/site_data/site_data_writer.h
index 2c9f6b0..7bd7176d 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_writer.h
+++ b/components/performance_manager/persistence/site_data/site_data_writer.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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_WRITER_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_WRITER_H_
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_WRITER_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_WRITER_H_
 
 #include <memory>
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_impl.h"
 
 namespace performance_manager {
 
@@ -66,4 +66,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_WRITER_H_
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_WRITER_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_writer_unittest.cc b/components/performance_manager/persistence/site_data/site_data_writer_unittest.cc
similarity index 95%
rename from chrome/browser/performance_manager/persistence/site_data/site_data_writer_unittest.cc
rename to components/performance_manager/persistence/site_data/site_data_writer_unittest.cc
index 08f4858..a388a485 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_writer_unittest.cc
+++ b/components/performance_manager/persistence/site_data/site_data_writer_unittest.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/performance_manager/persistence/site_data/site_data_writer.h"
+#include "components/performance_manager/persistence/site_data/site_data_writer.h"
 
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/time/time.h"
-#include "chrome/browser/performance_manager/persistence/site_data/feature_usage.h"
-#include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
-#include "chrome/browser/performance_manager/persistence/site_data/unittest_utils.h"
+#include "components/performance_manager/persistence/site_data/feature_usage.h"
+#include "components/performance_manager/persistence/site_data/site_data_impl.h"
+#include "components/performance_manager/persistence/site_data/unittest_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
diff --git a/components/performance_manager/persistence/site_data/tab_visibility.h b/components/performance_manager/persistence/site_data/tab_visibility.h
new file mode 100644
index 0000000..3aa4c4f
--- /dev/null
+++ b/components/performance_manager/persistence/site_data/tab_visibility.h
@@ -0,0 +1,14 @@
+// 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 COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_TAB_VISIBILITY_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_TAB_VISIBILITY_H_
+
+namespace performance_manager {
+
+enum class TabVisibility { kBackground, kForeground };
+
+}  // namespace performance_manager
+
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_TAB_VISIBILITY_H_
diff --git a/components/performance_manager/persistence/site_data/unittest_utils.cc b/components/performance_manager/persistence/site_data/unittest_utils.cc
new file mode 100644
index 0000000..3113bb9
--- /dev/null
+++ b/components/performance_manager/persistence/site_data/unittest_utils.cc
@@ -0,0 +1,49 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/performance_manager/persistence/site_data/unittest_utils.h"
+
+#include <utility>
+
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "components/performance_manager/performance_manager_impl.h"
+
+namespace performance_manager {
+namespace testing {
+
+MockSiteDataImplOnDestroyDelegate::MockSiteDataImplOnDestroyDelegate() =
+    default;
+MockSiteDataImplOnDestroyDelegate::~MockSiteDataImplOnDestroyDelegate() =
+    default;
+
+NoopSiteDataStore::NoopSiteDataStore() = default;
+NoopSiteDataStore::~NoopSiteDataStore() = default;
+
+void NoopSiteDataStore::ReadSiteDataFromStore(
+    const url::Origin& origin,
+    ReadSiteDataFromStoreCallback callback) {
+  std::move(callback).Run(base::nullopt);
+}
+
+void NoopSiteDataStore::WriteSiteDataIntoStore(
+    const url::Origin& origin,
+    const SiteDataProto& site_characteristic_proto) {}
+
+void NoopSiteDataStore::RemoveSiteDataFromStore(
+    const std::vector<url::Origin>& site_origins) {}
+
+void NoopSiteDataStore::ClearStore() {}
+
+void NoopSiteDataStore::GetStoreSize(GetStoreSizeCallback callback) {
+  std::move(callback).Run(base::nullopt, base::nullopt);
+}
+
+void NoopSiteDataStore::SetInitializationCallbackForTesting(
+    base::OnceClosure callback) {
+  std::move(callback).Run();
+}
+
+}  // namespace testing
+}  // namespace performance_manager
diff --git a/components/performance_manager/persistence/site_data/unittest_utils.h b/components/performance_manager/persistence/site_data/unittest_utils.h
new file mode 100644
index 0000000..a5451e4
--- /dev/null
+++ b/components/performance_manager/persistence/site_data/unittest_utils.h
@@ -0,0 +1,59 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_UNITTEST_UTILS_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_UNITTEST_UTILS_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "components/performance_manager/performance_manager_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_store.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace performance_manager {
+namespace testing {
+
+class MockSiteDataImplOnDestroyDelegate
+    : public internal::SiteDataImpl::OnDestroyDelegate {
+ public:
+  MockSiteDataImplOnDestroyDelegate();
+  ~MockSiteDataImplOnDestroyDelegate();
+
+  MOCK_METHOD1(OnSiteDataImplDestroyed, void(internal::SiteDataImpl*));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockSiteDataImplOnDestroyDelegate);
+};
+
+// An implementation of a SiteDataStore that doesn't record anything.
+class NoopSiteDataStore : public SiteDataStore {
+ public:
+  NoopSiteDataStore();
+  ~NoopSiteDataStore() override;
+
+  // SiteDataStore:
+  void ReadSiteDataFromStore(const url::Origin& origin,
+                             ReadSiteDataFromStoreCallback callback) override;
+  void WriteSiteDataIntoStore(
+      const url::Origin& origin,
+      const SiteDataProto& site_characteristic_proto) override;
+  void RemoveSiteDataFromStore(
+      const std::vector<url::Origin>& site_origins) override;
+  void ClearStore() override;
+  void GetStoreSize(GetStoreSizeCallback callback) override;
+  void SetInitializationCallbackForTesting(base::OnceClosure callback) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NoopSiteDataStore);
+};
+
+}  // namespace testing
+}  // namespace performance_manager
+
+#endif  // COMPONENTS_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_UNITTEST_UTILS_H_
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index e21597ed..51f9d633 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -665,6 +665,7 @@
         'DeviceLoginScreenScreenMagnifierType',
         'DeviceLoginScreenShowOptionsInSystemTrayMenu',
         'DeviceLoginScreenAccessibilityShortcutsEnabled',
+        'FloatingAccessibilityMenuEnabled',
       ],
     },
     {
@@ -9551,6 +9552,27 @@
       If the policy is left not set, all <ph name="PRODUCT_OS_NAME">$2<ex>Google Chrome OS</ex></ph> features will be enabled by default and the user can use any of them.''',
     },
     {
+      'name': 'FloatingAccessibilityMenuEnabled',
+      'owners': ['apotapchuk@chromium.org'],
+      'type': 'main',
+      'schema': { 'type': 'boolean' },
+      'supported_on': ['chrome_os:84-'],
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'example_value': True,
+      'id': 691,
+      'future': True,
+      'caption': '''Enables the floating accessibility menu''',
+      'tags': [],
+      'desc': '''In kiosk mode, controls whether the floating accessibility menu is being shown.
+
+      If this policy is set to enabled, the floating accessibility menu will be always shown.
+
+      If this policy is set to disabled or left unset, the floating accessibility menu will never be shown.''',
+    },
+    {
       'name': 'UserDisplayName',
       'owners': ['file://components/policy/resources/OWNERS'],
       'type': 'string',
@@ -21302,6 +21324,6 @@
   ],
   'placeholders': [],
   'deleted_policy_ids': [412, 546, 562, 569, 578],
-  'highest_id_currently_used': 690,
+  'highest_id_currently_used': 691,
   'highest_atomic_group_id_currently_used': 38
 }
diff --git a/components/renderer_context_menu/render_view_context_menu_base.cc b/components/renderer_context_menu/render_view_context_menu_base.cc
index d764864..b081d25 100644
--- a/components/renderer_context_menu/render_view_context_menu_base.cc
+++ b/components/renderer_context_menu/render_view_context_menu_base.cc
@@ -18,6 +18,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/menu_item.h"
 #include "ppapi/buildflags/buildflags.h"
+#include "ui/base/models/image_model.h"
 
 using blink::WebString;
 using blink::WebURL;
@@ -207,14 +208,16 @@
     int command_id,
     const base::string16& title,
     const gfx::ImageSkia& image) {
-  menu_model_.AddItemWithIcon(command_id, title, image);
+  menu_model_.AddItemWithIcon(command_id, title,
+                              ui::ImageModel::FromImageSkia(image));
 }
 
 void RenderViewContextMenuBase::AddMenuItemWithIcon(
     int command_id,
     const base::string16& title,
     const gfx::VectorIcon& icon) {
-  menu_model_.AddItemWithIcon(command_id, title, icon);
+  menu_model_.AddItemWithIcon(command_id, title,
+                              ui::ImageModel::FromVectorIcon(icon));
 }
 
 void RenderViewContextMenuBase::AddCheckItem(int command_id,
@@ -237,8 +240,8 @@
     int message_id,
     ui::MenuModel* model,
     const gfx::ImageSkia& image) {
-  menu_model_.AddSubMenuWithStringIdAndIcon(command_id, message_id, model,
-                                            image);
+  menu_model_.AddSubMenuWithStringIdAndIcon(
+      command_id, message_id, model, ui::ImageModel::FromImageSkia(image));
 }
 
 void RenderViewContextMenuBase::AddSubMenuWithStringIdAndIcon(
@@ -246,8 +249,8 @@
     int message_id,
     ui::MenuModel* model,
     const gfx::VectorIcon& icon) {
-  menu_model_.AddSubMenuWithStringIdAndIcon(command_id, message_id, model,
-                                            icon);
+  menu_model_.AddSubMenuWithStringIdAndIcon(
+      command_id, message_id, model, ui::ImageModel::FromVectorIcon(icon));
 }
 
 void RenderViewContextMenuBase::UpdateMenuItem(int command_id,
@@ -271,7 +274,7 @@
   if (index == -1)
     return;
 
-  menu_model_.SetIcon(index, image);
+  menu_model_.SetIcon(index, ui::ImageModel::FromImage(image));
 #if defined(OS_CHROMEOS)
   if (toolkit_delegate_)
     toolkit_delegate_->RebuildMenu();
diff --git a/components/sync/engine_impl/sync_encryption_handler_impl.cc b/components/sync/engine_impl/sync_encryption_handler_impl.cc
index 0d7a8eb..ff2e166c 100644
--- a/components/sync/engine_impl/sync_encryption_handler_impl.cc
+++ b/components/sync/engine_impl/sync_encryption_handler_impl.cc
@@ -406,11 +406,6 @@
                               MIGRATION_STATE_SIZE);
   }
 
-  if (!IsNigoriMigratedToKeystore(node.GetNigoriSpecifics())) {
-    UMA_HISTOGRAM_BOOLEAN("Sync.NigoriMigrationAttemptedBeforeNotMigrated",
-                          migration_attempted_);
-  }
-
   // Always trigger an encrypted types and cryptographer state change event at
   // init time so observers get the initial values.
   for (auto& observer : observers_) {
@@ -1691,8 +1686,6 @@
   if (migration_reason == NigoriMigrationReason::kNoReason)
     return false;
 
-  UMA_HISTOGRAM_ENUMERATION("Sync.NigoriMigrationReason", migration_reason);
-  UMA_HISTOGRAM_ENUMERATION("Sync.NigoriMigrationTrigger", migration_trigger);
   migration_attempted_ = true;
 
   DVLOG(1) << "Starting nigori migration to keystore support.";
@@ -1871,12 +1864,6 @@
       NOTREACHED();
       break;
   }
-  UMA_HISTOGRAM_BOOLEAN("Sync.IsNigoriMigratedAfterMigration",
-                        IsNigoriMigratedToKeystore(migrated_nigori));
-  UMA_HISTOGRAM_BOOLEAN(
-      "Sync.ShouldTriggerMigrationAfterMigration",
-      GetMigrationReason(migrated_nigori, *cryptographer, *passphrase_type) !=
-          NigoriMigrationReason::kNoReason);
   return true;
 }
 
diff --git a/components/sync/model/model_type_change_processor.h b/components/sync/model/model_type_change_processor.h
index cb4b099..0815125 100644
--- a/components/sync/model/model_type_change_processor.h
+++ b/components/sync/model/model_type_change_processor.h
@@ -70,10 +70,6 @@
 
   // Returns true if a tracked entity has local changes. A commit may or may not
   // be in progress at this time.
-  // TODO(mastiz): The only user of this is HISTORY_DELETE_DIRECTIVES which
-  // needs it for a rather questionable reason. Revisit this, for example by
-  // moving the SyncableService to history's backend thread, and leveraging
-  // USS's ability to delete local data upcon commit completion.
   virtual bool IsEntityUnsynced(const std::string& storage_key) = 0;
 
   // Returns the creation timestamp of the sync entity, or a null time if the
diff --git a/components/sync/protocol/entity_metadata.proto b/components/sync/protocol/entity_metadata.proto
index 2b579f3e..5ceb855 100644
--- a/components/sync/protocol/entity_metadata.proto
+++ b/components/sync/protocol/entity_metadata.proto
@@ -71,4 +71,9 @@
   // unique_position.proto for more information about its internal
   // representation.
   optional UniquePosition unique_position = 11;
+
+  // Used only for bookmarks. It's analogous to |specifics_hash| but it
+  // exclusively hashes the content of the favicon image, as represented in
+  // proto field BookmarkSpecifics.favicon, using base::PersistentHash().
+  optional fixed32 bookmark_favicon_hash = 12;
 }
diff --git a/components/sync_bookmarks/BUILD.gn b/components/sync_bookmarks/BUILD.gn
index 5f90a4c..7785fc1 100644
--- a/components/sync_bookmarks/BUILD.gn
+++ b/components/sync_bookmarks/BUILD.gn
@@ -62,7 +62,9 @@
     "//components/prefs:test_support",
     "//components/sync:test_support",
     "//components/undo",
+    "//skia",
     "//testing/gmock",
     "//testing/gtest",
+    "//ui/gfx",
   ]
 }
diff --git a/components/sync_bookmarks/DEPS b/components/sync_bookmarks/DEPS
index bc162d7..09ded9e 100644
--- a/components/sync_bookmarks/DEPS
+++ b/components/sync_bookmarks/DEPS
@@ -7,6 +7,7 @@
   "+components/prefs",
   "+components/sync",
   "+components/undo",
+  "+third_party/skia/include",
   "+ui/base",
   "+ui/gfx",
 ]
diff --git a/components/sync_bookmarks/bookmark_model_observer_impl.cc b/components/sync_bookmarks/bookmark_model_observer_impl.cc
index b9bd1605..4838904 100644
--- a/components/sync_bookmarks/bookmark_model_observer_impl.cc
+++ b/components/sync_bookmarks/bookmark_model_observer_impl.cc
@@ -15,7 +15,6 @@
 #include "components/sync/driver/sync_driver_switches.h"
 #include "components/sync/engine/non_blocking_sync_common.h"
 #include "components/sync_bookmarks/bookmark_specifics_conversions.h"
-#include "components/sync_bookmarks/synced_bookmark_tracker.h"
 
 namespace sync_bookmarks {
 
@@ -184,25 +183,9 @@
     return;
   }
 
-  const base::Time modification_time = base::Time::Now();
   sync_pb::EntitySpecifics specifics = CreateSpecificsFromBookmarkNode(
       node, model, /*force_favicon_load=*/true, entity->has_final_guid());
-
-  // TODO(crbug.com/516866): The below CHECKs are added to debug some crashes.
-  // Should be removed after figuring out the reason for the crash.
-  CHECK_EQ(entity, bookmark_tracker_->GetEntityForBookmarkNode(node));
-  if (entity->MatchesSpecificsHash(specifics)) {
-    // We should push data to the server only if there is an actual change in
-    // the data. We could hit this code path without having actual changes
-    // (e.g.upon a favicon load).
-    return;
-  }
-  bookmark_tracker_->Update(entity, entity->metadata()->server_version(),
-                            modification_time,
-                            entity->metadata()->unique_position(), specifics);
-  // Mark the entity that it needs to be committed.
-  bookmark_tracker_->IncrementSequenceNumber(entity);
-  nudge_for_commit_closure_.Run();
+  ProcessUpdate(entity, specifics);
 }
 
 void BookmarkModelObserverImpl::BookmarkMetaInfoChanged(
@@ -229,7 +212,26 @@
     model->GetFavicon(node);
     return;
   }
-  BookmarkNodeChanged(model, node);
+
+  const SyncedBookmarkTracker::Entity* entity =
+      bookmark_tracker_->GetEntityForBookmarkNode(node);
+  if (!entity) {
+    // This should be practically unreachable but in theory it's possible that a
+    // favicon changes *during* the creation of a bookmark (by another
+    // observer). See analogous codepath in BookmarkNodeChanged().
+    return;
+  }
+
+  const sync_pb::EntitySpecifics specifics = CreateSpecificsFromBookmarkNode(
+      node, model, /*force_favicon_load=*/false, entity->has_final_guid());
+
+  if (entity->MatchesFaviconHash(specifics.bookmark().favicon())) {
+    // The favicon content didn't actually change, which means this event is
+    // almost certainly the result of favicon loading having completed.
+    return;
+  }
+
+  ProcessUpdate(entity, specifics);
 }
 
 void BookmarkModelObserverImpl::BookmarkNodeChildrenReordered(
@@ -339,6 +341,34 @@
       suffix);
 }
 
+void BookmarkModelObserverImpl::ProcessUpdate(
+    const SyncedBookmarkTracker::Entity* entity,
+    const sync_pb::EntitySpecifics& specifics) {
+  DCHECK(entity);
+
+  // Data should be committed to the server only if there is an actual change,
+  // determined here by comparing hashes.
+  if (entity->MatchesSpecificsHash(specifics)) {
+    // Specifics haven't actually changed, so the local change can be ignored.
+    //
+    // This is an opportunity to populate the favicon hash in sync metadata if
+    // it hasn't been populated yet. This is needed because the proto field that
+    // stores favicon hashes was introduced late. The fact that hashed specifics
+    // match implies that the favicon (which is part of specifics) must also
+    // match, hence the proto field can be safely populated.
+    bookmark_tracker_->PopulateFaviconHashIfUnset(
+        entity, specifics.bookmark().favicon());
+    return;
+  }
+
+  bookmark_tracker_->Update(entity, entity->metadata()->server_version(),
+                            /*modification_time=*/base::Time::Now(),
+                            entity->metadata()->unique_position(), specifics);
+  // Mark the entity that it needs to be committed.
+  bookmark_tracker_->IncrementSequenceNumber(entity);
+  nudge_for_commit_closure_.Run();
+}
+
 void BookmarkModelObserverImpl::ProcessDelete(
     const bookmarks::BookmarkNode* parent,
     const bookmarks::BookmarkNode* node) {
diff --git a/components/sync_bookmarks/bookmark_model_observer_impl.h b/components/sync_bookmarks/bookmark_model_observer_impl.h
index 4fdaa92c..512d25e9 100644
--- a/components/sync_bookmarks/bookmark_model_observer_impl.h
+++ b/components/sync_bookmarks/bookmark_model_observer_impl.h
@@ -11,6 +11,7 @@
 #include "base/callback.h"
 #include "components/bookmarks/browser/bookmark_model_observer.h"
 #include "components/bookmarks/browser/bookmark_node.h"
+#include "components/sync_bookmarks/synced_bookmark_tracker.h"
 #include "url/gurl.h"
 
 namespace syncer {
@@ -19,8 +20,6 @@
 
 namespace sync_bookmarks {
 
-class SyncedBookmarkTracker;
-
 // Class for listening to local changes in the bookmark model and updating
 // metadata in SyncedBookmarkTracker, such that ultimately the processor exposes
 // those local changes to the sync engine.
@@ -72,6 +71,12 @@
                                          size_t index,
                                          const std::string& sync_id);
 
+  // Process a modification of a local node and updates |bookmark_tracker_|
+  // accordingly. No-op if the commit can be optimized away, i.e. if |specifics|
+  // are identical to the previously-known specifics (in hashed form).
+  void ProcessUpdate(const SyncedBookmarkTracker::Entity* entity,
+                     const sync_pb::EntitySpecifics& specifics);
+
   // Processes the deletion of a bookmake node and updates the
   // |bookmark_tracker_| accordingly. If |node| is a bookmark, it gets marked
   // as deleted and that it requires a commit. If it's a folder, it recurses
diff --git a/components/sync_bookmarks/bookmark_model_observer_impl_unittest.cc b/components/sync_bookmarks/bookmark_model_observer_impl_unittest.cc
index e0b85b35..fb67a94 100644
--- a/components/sync_bookmarks/bookmark_model_observer_impl_unittest.cc
+++ b/components/sync_bookmarks/bookmark_model_observer_impl_unittest.cc
@@ -5,6 +5,8 @@
 #include "components/sync_bookmarks/bookmark_model_observer_impl.h"
 
 #include <algorithm>
+#include <list>
+#include <map>
 #include <memory>
 #include <utility>
 #include <vector>
@@ -19,16 +21,20 @@
 #include "components/sync_bookmarks/synced_bookmark_tracker.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/image/image.h"
 
 namespace sync_bookmarks {
 
 namespace {
 
+using testing::ElementsAre;
 using testing::Eq;
+using testing::IsEmpty;
 using testing::Ne;
 using testing::NiceMock;
 using testing::NotNull;
-using testing::ElementsAre;
+using testing::UnorderedElementsAre;
 
 const char kBookmarkBarId[] = "bookmark_bar_id";
 const char kBookmarkBarTag[] = "bookmark_bar";
@@ -38,6 +44,61 @@
 const char kMobileBookmarksTag[] = "synced_bookmarks";
 const size_t kMaxEntries = 1000;
 
+// Matches |arg| of type SyncedBookmarkTracker::Entity*.
+MATCHER_P(HasBookmarkNode, node, "") {
+  return arg->bookmark_node() == node;
+}
+
+// Returns a single-color 16x16 image using |color|.
+gfx::Image CreateTestImage(SkColor color) {
+  SkBitmap bitmap;
+  bitmap.allocN32Pixels(16, 16);
+  bitmap.eraseColor(color);
+  return gfx::Image::CreateFrom1xBitmap(bitmap);
+}
+
+// Extension of TestBookmarkClient with basic functionality to test favicon
+// loading.
+class TestBookmarkClientWithFavicon : public bookmarks::TestBookmarkClient {
+ public:
+  // Mimics the completion of a previously-triggered GetFaviconImageForPageURL()
+  // call for |page_url|, usually invoked by BookmarkModel. Returns false if no
+  // such a call is pending completion. The completion returns a favicon with
+  // URL |icon_url| and a single-color 16x16 image using |color|.
+  bool SimulateFaviconLoaded(const GURL& page_url,
+                             const GURL& icon_url,
+                             SkColor color) {
+    if (requests_per_page_url_[page_url].empty()) {
+      return false;
+    }
+
+    favicon_base::FaviconImageCallback callback =
+        std::move(requests_per_page_url_[page_url].front());
+    requests_per_page_url_[page_url].pop_front();
+
+    favicon_base::FaviconImageResult result;
+    result.image = CreateTestImage(color);
+    result.icon_url = icon_url;
+    std::move(callback).Run(result);
+    return true;
+  }
+
+  // bookmarks::TestBookmarkClient implementation.
+  base::CancelableTaskTracker::TaskId GetFaviconImageForPageURL(
+      const GURL& page_url,
+      favicon_base::IconType type,
+      favicon_base::FaviconImageCallback callback,
+      base::CancelableTaskTracker* tracker) override {
+    requests_per_page_url_[page_url].push_back(std::move(callback));
+    return next_task_id_++;
+  }
+
+ private:
+  base::CancelableTaskTracker::TaskId next_task_id_ = 1;
+  std::map<GURL, std::list<favicon_base::FaviconImageCallback>>
+      requests_per_page_url_;
+};
+
 class BookmarkModelObserverImplTest : public testing::Test {
  public:
   BookmarkModelObserverImplTest()
@@ -46,7 +107,8 @@
         observer_(nudge_for_commit_closure_.Get(),
                   /*on_bookmark_model_being_deleted_closure=*/base::DoNothing(),
                   bookmark_tracker_.get()),
-        bookmark_model_(bookmarks::TestBookmarkClient::CreateModel()) {
+        bookmark_model_(bookmarks::TestBookmarkClient::CreateModelWithClient(
+            std::make_unique<TestBookmarkClientWithFavicon>())) {
     bookmark_model_->AddObserver(&observer_);
     sync_pb::EntitySpecifics specifics;
     specifics.mutable_bookmark()->set_legacy_canonicalized_title(
@@ -90,9 +152,10 @@
          bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries)) {
       const std::string id = entity->metadata()->server_id();
       // Don't simulate change in id for simplicity.
-      bookmark_tracker()->UpdateUponCommitResponse(entity, id,
-                                                   /*server_version=*/1,
-                                                   /*acked_sequence_number=*/1);
+      bookmark_tracker()->UpdateUponCommitResponse(
+          entity, id,
+          /*server_version=*/1,
+          /*acked_sequence_number=*/entity->metadata()->sequence_number());
     }
   }
 
@@ -110,6 +173,10 @@
   base::MockCallback<base::RepeatingClosure>* nudge_for_commit_closure() {
     return &nudge_for_commit_closure_;
   }
+  TestBookmarkClientWithFavicon* bookmark_client() {
+    return static_cast<TestBookmarkClientWithFavicon*>(
+        bookmark_model_->client());
+  }
 
  private:
   NiceMock<base::MockCallback<base::RepeatingClosure>>
@@ -174,29 +241,17 @@
   EXPECT_CALL(*nudge_for_commit_closure(), Run());
   bookmark_model()->SetTitle(bookmark_node2, base::UTF8ToUTF16(kNewTitle2));
   // Node 2 should be in the local changes list.
-  std::vector<const SyncedBookmarkTracker::Entity*> local_changes =
-      bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries);
-  ASSERT_THAT(local_changes.size(), 1U);
-  EXPECT_THAT(local_changes[0]->bookmark_node(), Eq(bookmark_node2));
+  EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries),
+              ElementsAre(HasBookmarkNode(bookmark_node2)));
 
   // Now update the url of the 1st node.
   EXPECT_CALL(*nudge_for_commit_closure(), Run());
   bookmark_model()->SetURL(bookmark_node1, GURL(kNewUrl1));
 
   // Node 1 and 2 should be in the local changes list.
-  local_changes = bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries);
-  ASSERT_THAT(local_changes.size(), 2U);
-
-  // Constuct a set of the bookmark nodes in the local changes.
-  std::set<const bookmarks::BookmarkNode*> nodes_in_local_changes;
-  for (const SyncedBookmarkTracker::Entity* entity : local_changes) {
-    nodes_in_local_changes.insert(entity->bookmark_node());
-  }
-  // Both bookmarks should exist in the set.
-  EXPECT_TRUE(nodes_in_local_changes.find(bookmark_node1) !=
-              nodes_in_local_changes.end());
-  EXPECT_TRUE(nodes_in_local_changes.find(bookmark_node2) !=
-              nodes_in_local_changes.end());
+  EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries),
+              UnorderedElementsAre(HasBookmarkNode(bookmark_node1),
+                                   HasBookmarkNode(bookmark_node2)));
 
   // Now update metainfo of the 1st node.
   EXPECT_CALL(*nudge_for_commit_closure(), Run());
@@ -286,21 +341,11 @@
   EXPECT_TRUE(PositionOf(nodes[3]).LessThan(PositionOf(nodes[0])));
   EXPECT_TRUE(PositionOf(nodes[0]).LessThan(PositionOf(nodes[2])));
 
-  std::vector<const SyncedBookmarkTracker::Entity*> local_changes =
-      bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries);
-  ASSERT_THAT(local_changes.size(), nodes.size());
-
-  // Constuct a set of the bookmark nodes in the local changes.
-  std::set<const bookmarks::BookmarkNode*> nodes_in_local_changes;
-  for (const SyncedBookmarkTracker::Entity* entity : local_changes) {
-    nodes_in_local_changes.insert(entity->bookmark_node());
-  }
-
-  // All reordered nodes should exist in the set of local changes to be
-  // committed.
-  for (const bookmarks::BookmarkNode* node : nodes) {
-    EXPECT_THAT(nodes_in_local_changes.count(node), Ne(0U));
-  }
+  // All 4 nodes should have local changes to commit.
+  EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries),
+              UnorderedElementsAre(
+                  HasBookmarkNode(nodes[0]), HasBookmarkNode(nodes[1]),
+                  HasBookmarkNode(nodes[2]), HasBookmarkNode(nodes[3])));
 }
 
 TEST_F(BookmarkModelObserverImplTest,
@@ -376,16 +421,11 @@
                   ->metadata()
                   ->is_deleted());
 
-  // folder2, bookmark2, and bookmark3 should be in the local changes list.
-  std::vector<const SyncedBookmarkTracker::Entity*> local_changes =
-      bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries);
-  ASSERT_THAT(local_changes.size(), 3U);
-
-  // All deleted nodes entities should exist in the set of local changes to be
+  // folder2, bookmark2, and bookmark3 should be in the local changes to be
   // committed and folder2 deletion should be the last one (after all children
   // deletions).
   EXPECT_THAT(
-      local_changes,
+      bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries),
       ElementsAre(bookmark_tracker()->GetEntityForSyncId(bookmark2_entity_id),
                   bookmark_tracker()->GetEntityForSyncId(bookmark3_entity_id),
                   bookmark_tracker()->GetEntityForSyncId(folder2_entity_id)));
@@ -644,6 +684,96 @@
   observer.BookmarkModelBeingDeleted(/*model=*/nullptr);
 }
 
+TEST_F(BookmarkModelObserverImplTest, ShouldNotIssueCommitUponFaviconLoad) {
+  const GURL kBookmarkUrl("http://www.url.com");
+  const GURL kIconUrl("http://www.url.com/favicon.ico");
+  const SkColor kColor = SK_ColorRED;
+
+  const bookmarks::BookmarkNode* bookmark_bar_node =
+      bookmark_model()->bookmark_bar_node();
+  const bookmarks::BookmarkNode* bookmark_node = bookmark_model()->AddURL(
+      /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16("title"),
+      kBookmarkUrl);
+
+  ASSERT_TRUE(
+      bookmark_client()->SimulateFaviconLoaded(kBookmarkUrl, kIconUrl, kColor));
+  SimulateCommitResponseForAllLocalChanges();
+  ASSERT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries),
+              IsEmpty());
+
+  const SyncedBookmarkTracker::Entity* entity =
+      bookmark_tracker()->GetEntityForBookmarkNode(bookmark_node);
+  ASSERT_THAT(entity, NotNull());
+  ASSERT_TRUE(entity->metadata()->has_bookmark_favicon_hash());
+  const uint32_t initial_favicon_hash =
+      entity->metadata()->bookmark_favicon_hash();
+
+  // Clear the specifics hash (as if the proto definition would have changed).
+  // This is needed because otherwise the commit is trivially optimized away
+  // (i.e. literally nothing changed).
+  bookmark_tracker()->ClearSpecificsHashForTest(entity);
+
+  // Mimic the very same favicon being loaded again (similar to a startup
+  // scenario). Note that OnFaviconsChanged() needs no icon URL to invalidate
+  // the favicon of a bookmark.
+  EXPECT_CALL(*nudge_for_commit_closure(), Run()).Times(0);
+  bookmark_model()->OnFaviconsChanged(/*page_urls=*/{kBookmarkUrl},
+                                      /*icon_url=*/GURL());
+  ASSERT_TRUE(bookmark_node->is_favicon_loading());
+  ASSERT_TRUE(
+      bookmark_client()->SimulateFaviconLoaded(kBookmarkUrl, kIconUrl, kColor));
+
+  EXPECT_TRUE(entity->metadata()->has_bookmark_favicon_hash());
+  EXPECT_THAT(entity->metadata()->bookmark_favicon_hash(),
+              Eq(initial_favicon_hash));
+  EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries),
+              IsEmpty());
+}
+
+TEST_F(BookmarkModelObserverImplTest, ShouldCommitLocalFaviconChange) {
+  const GURL kBookmarkUrl("http://www.url.com");
+  const GURL kInitialIconUrl("http://www.url.com/initial.ico");
+  const GURL kFinalIconUrl("http://www.url.com/final.ico");
+
+  const bookmarks::BookmarkNode* bookmark_bar_node =
+      bookmark_model()->bookmark_bar_node();
+  const bookmarks::BookmarkNode* bookmark_node = bookmark_model()->AddURL(
+      /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16("title"),
+      kBookmarkUrl);
+
+  ASSERT_TRUE(bookmark_node->is_favicon_loading());
+  ASSERT_TRUE(bookmark_client()->SimulateFaviconLoaded(
+      kBookmarkUrl, kInitialIconUrl, SK_ColorRED));
+  SimulateCommitResponseForAllLocalChanges();
+  ASSERT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries),
+              IsEmpty());
+
+  const SyncedBookmarkTracker::Entity* entity =
+      bookmark_tracker()->GetEntityForBookmarkNode(bookmark_node);
+  ASSERT_THAT(entity, NotNull());
+  ASSERT_TRUE(entity->metadata()->has_bookmark_favicon_hash());
+  const uint32_t initial_favicon_hash =
+      entity->metadata()->bookmark_favicon_hash();
+
+  // A favicon change should trigger a commit nudge once the favicon loads, but
+  // not earlier. Note that OnFaviconsChanged() needs no icon URL to invalidate
+  // the favicon of a bookmark.
+  EXPECT_CALL(*nudge_for_commit_closure(), Run()).Times(0);
+  bookmark_model()->OnFaviconsChanged(/*page_urls=*/{kBookmarkUrl},
+                                      /*icon_url=*/GURL());
+  ASSERT_TRUE(bookmark_node->is_favicon_loading());
+
+  EXPECT_CALL(*nudge_for_commit_closure(), Run());
+  ASSERT_TRUE(bookmark_client()->SimulateFaviconLoaded(
+      kBookmarkUrl, kFinalIconUrl, SK_ColorBLUE));
+
+  EXPECT_TRUE(entity->metadata()->has_bookmark_favicon_hash());
+  EXPECT_THAT(entity->metadata()->bookmark_favicon_hash(),
+              Ne(initial_favicon_hash));
+  EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries),
+              ElementsAre(HasBookmarkNode(bookmark_node)));
+}
+
 }  // namespace
 
 }  // namespace sync_bookmarks
diff --git a/components/sync_bookmarks/synced_bookmark_tracker.cc b/components/sync_bookmarks/synced_bookmark_tracker.cc
index d5e1958d..2ae5991 100644
--- a/components/sync_bookmarks/synced_bookmark_tracker.cc
+++ b/components/sync_bookmarks/synced_bookmark_tracker.cc
@@ -9,6 +9,7 @@
 #include <unordered_map>
 
 #include "base/base64.h"
+#include "base/hash/hash.h"
 #include "base/hash/sha1.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
@@ -122,6 +123,13 @@
   return hash == metadata_->specifics_hash();
 }
 
+bool SyncedBookmarkTracker::Entity::MatchesFaviconHash(
+    const std::string& favicon_png_bytes) const {
+  DCHECK(!metadata_->is_deleted());
+  return metadata_->bookmark_favicon_hash() ==
+         base::PersistentHash(favicon_png_bytes);
+}
+
 bool SyncedBookmarkTracker::Entity::has_final_guid() const {
   return metadata_->has_client_tag_hash();
 }
@@ -139,6 +147,15 @@
       syncer::ClientTagHash::FromUnhashed(syncer::BOOKMARKS, guid).value());
 }
 
+void SyncedBookmarkTracker::Entity::PopulateFaviconHashIfUnset(
+    const std::string& favicon_png_bytes) {
+  if (metadata_->has_bookmark_favicon_hash()) {
+    return;
+  }
+
+  metadata_->set_bookmark_favicon_hash(base::PersistentHash(favicon_png_bytes));
+}
+
 size_t SyncedBookmarkTracker::Entity::EstimateMemoryUsage() const {
   using base::trace_event::EstimateMemoryUsage;
   size_t memory_usage = 0;
@@ -241,6 +258,8 @@
                                     syncer::BOOKMARKS, bookmark_node->guid())
                                     .value());
   HashSpecifics(specifics, metadata->mutable_specifics_hash());
+  metadata->set_bookmark_favicon_hash(
+      base::PersistentHash(specifics.bookmark().favicon()));
   auto entity = std::make_unique<Entity>(bookmark_node, std::move(metadata));
   CHECK_EQ(0U, bookmark_node_to_entities_map_.count(bookmark_node));
   bookmark_node_to_entities_map_[bookmark_node] = entity.get();
@@ -268,6 +287,8 @@
   *mutable_entity->metadata()->mutable_unique_position() = unique_position;
   HashSpecifics(specifics,
                 mutable_entity->metadata()->mutable_specifics_hash());
+  mutable_entity->metadata()->set_bookmark_favicon_hash(
+      base::PersistentHash(specifics.bookmark().favicon()));
   // TODO(crbug.com/516866): in case of conflict, the entity might exist in
   // |ordered_local_tombstones_| as well if it has been locally deleted.
 }
@@ -284,6 +305,13 @@
   AsMutableEntity(entity)->set_final_guid(guid);
 }
 
+void SyncedBookmarkTracker::PopulateFaviconHashIfUnset(
+    const Entity* entity,
+    const std::string& favicon_png_bytes) {
+  DCHECK(entity);
+  AsMutableEntity(entity)->PopulateFaviconHashIfUnset(favicon_png_bytes);
+}
+
 void SyncedBookmarkTracker::MarkCommitMayHaveStarted(const Entity* entity) {
   DCHECK(entity);
   AsMutableEntity(entity)->set_commit_may_have_started(true);
@@ -297,6 +325,7 @@
 
   Entity* mutable_entity = AsMutableEntity(entity);
   mutable_entity->metadata()->set_is_deleted(true);
+  mutable_entity->metadata()->clear_bookmark_favicon_hash();
   // Clear all references to the deleted bookmark node.
   bookmark_node_to_entities_map_.erase(mutable_entity->bookmark_node());
   mutable_entity->clear_bookmark_node();
@@ -731,6 +760,10 @@
   return ordered_local_tombstones_.size();
 }
 
+void SyncedBookmarkTracker::ClearSpecificsHashForTest(const Entity* entity) {
+  AsMutableEntity(entity)->metadata()->clear_specifics_hash();
+}
+
 void SyncedBookmarkTracker::CheckAllNodesTracked(
     const bookmarks::BookmarkModel* bookmark_model) const {
   // TODO(crbug.com/516866): The method is added to debug some crashes.
diff --git a/components/sync_bookmarks/synced_bookmark_tracker.h b/components/sync_bookmarks/synced_bookmark_tracker.h
index 465448d..936841f 100644
--- a/components/sync_bookmarks/synced_bookmark_tracker.h
+++ b/components/sync_bookmarks/synced_bookmark_tracker.h
@@ -56,6 +56,10 @@
     // Check whether |specifics| matches the stored specifics_hash.
     bool MatchesSpecificsHash(const sync_pb::EntitySpecifics& specifics) const;
 
+    // Check whether |favicon_png_bytes| matches the stored
+    // bookmark_favicon_hash.
+    bool MatchesFaviconHash(const std::string& favicon_png_bytes) const;
+
     // Returns null for tombstones.
     const bookmarks::BookmarkNode* bookmark_node() const {
       return bookmark_node_;
@@ -105,6 +109,8 @@
     // otherwise).
     void set_final_guid(const std::string& guid);
 
+    void PopulateFaviconHashIfUnset(const std::string& favicon_png_bytes);
+
     // Returns the estimate of dynamically allocated memory in bytes.
     size_t EstimateMemoryUsage() const;
 
@@ -173,6 +179,11 @@
   // Populates a bookmark's final GUID. |entity| must be owned by this tracker.
   void PopulateFinalGuid(const Entity* entity, const std::string& guid);
 
+  // Populates the metadata field representing the hashed favicon. This method
+  // is effectively used to backfill the proto field, which was introduced late.
+  void PopulateFaviconHashIfUnset(const Entity* entity,
+                                  const std::string& favicon_png_bytes);
+
   // Marks an existing entry that a commit request might have been sent to the
   // server. |entity| must be owned by this tracker.
   void MarkCommitMayHaveStarted(const Entity* entity);
@@ -250,6 +261,9 @@
   // confirmed the deletion yet.
   size_t TrackedUncommittedTombstonesCountForDebugging() const;
 
+  // Clears the specifics hash for |entity|, useful for testing.
+  void ClearSpecificsHashForTest(const Entity* entity);
+
   // Checks whther all nodes in |bookmark_model| that *should* be tracked as per
   // CanSyncNode() are tracked.
   void CheckAllNodesTracked(
diff --git a/components/sync_bookmarks/synced_bookmark_tracker_unittest.cc b/components/sync_bookmarks/synced_bookmark_tracker_unittest.cc
index 3002ca1..d15c5b3f 100644
--- a/components/sync_bookmarks/synced_bookmark_tracker_unittest.cc
+++ b/components/sync_bookmarks/synced_bookmark_tracker_unittest.cc
@@ -765,6 +765,123 @@
       /*sample=*/ExpectedCorruptionReason::NO_CORRUPTION, /*count=*/1);
 }
 
+TEST(SyncedBookmarkTrackerTest,
+     ShouldPopulateFaviconHashForNewlyAddedEntities) {
+  std::unique_ptr<SyncedBookmarkTracker> tracker =
+      SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
+
+  const std::string kSyncId = "SYNC_ID";
+  const std::string kTitle = "Title";
+  const GURL kUrl("http://www.foo.com");
+  const int64_t kId = 1;
+  const int64_t kServerVersion = 1000;
+  const base::Time kCreationTime = base::Time::Now();
+  const syncer::UniquePosition kUniquePosition =
+      syncer::UniquePosition::InitialPosition(
+          syncer::UniquePosition::RandomSuffix());
+  const std::string kFaviconPngBytes = "fakefaviconbytes";
+
+  sync_pb::EntitySpecifics specifics = GenerateSpecifics(kTitle, kUrl.spec());
+  specifics.mutable_bookmark()->set_favicon(kFaviconPngBytes);
+
+  bookmarks::BookmarkNode node(kId, base::GenerateGUID(), kUrl);
+  const SyncedBookmarkTracker::Entity* entity =
+      tracker->Add(&node, kSyncId, kServerVersion, kCreationTime,
+                   kUniquePosition.ToProto(), specifics);
+
+  EXPECT_TRUE(entity->metadata()->has_bookmark_favicon_hash());
+  EXPECT_TRUE(entity->MatchesFaviconHash(kFaviconPngBytes));
+  EXPECT_FALSE(entity->MatchesFaviconHash("otherhash"));
+}
+
+TEST(SyncedBookmarkTrackerTest, ShouldPopulateFaviconHashUponUpdate) {
+  const std::string kSyncId = "SYNC_ID";
+  const std::string kTitle = "Title";
+  const GURL kUrl("http://www.foo.com");
+  const int64_t kServerVersion = 1000;
+  const base::Time kModificationTime = base::Time::Now();
+  const syncer::UniquePosition kUniquePosition =
+      syncer::UniquePosition::InitialPosition(
+          syncer::UniquePosition::RandomSuffix());
+  const std::string kFaviconPngBytes = "fakefaviconbytes";
+
+  std::unique_ptr<bookmarks::BookmarkModel> model =
+      bookmarks::TestBookmarkClient::CreateModel();
+
+  const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
+  const bookmarks::BookmarkNode* node =
+      model->AddURL(/*parent=*/bookmark_bar_node, /*index=*/0,
+                    base::ASCIIToUTF16("Title"), GURL("http://www.url.com"));
+
+  sync_pb::BookmarkModelMetadata model_metadata =
+      CreateMetadataForPermanentNodes(model.get());
+
+  // Add entry for the URL node.
+  *model_metadata.add_bookmarks_metadata() =
+      CreateNodeMetadata(node->id(), kSyncId);
+
+  std::unique_ptr<SyncedBookmarkTracker> tracker =
+      SyncedBookmarkTracker::CreateFromBookmarkModelAndMetadata(
+          model.get(), std::move(model_metadata));
+  ASSERT_THAT(tracker, NotNull());
+
+  const SyncedBookmarkTracker::Entity* entity =
+      tracker->GetEntityForSyncId(kSyncId);
+  ASSERT_THAT(entity, NotNull());
+  ASSERT_FALSE(entity->metadata()->has_bookmark_favicon_hash());
+  ASSERT_FALSE(entity->MatchesFaviconHash(kFaviconPngBytes));
+
+  sync_pb::EntitySpecifics specifics = GenerateSpecifics(kTitle, kUrl.spec());
+  specifics.mutable_bookmark()->set_favicon(kFaviconPngBytes);
+
+  tracker->Update(entity, kServerVersion, kModificationTime,
+                  kUniquePosition.ToProto(), specifics);
+
+  EXPECT_TRUE(entity->metadata()->has_bookmark_favicon_hash());
+  EXPECT_TRUE(entity->MatchesFaviconHash(kFaviconPngBytes));
+  EXPECT_FALSE(entity->MatchesFaviconHash("otherhash"));
+}
+
+TEST(SyncedBookmarkTrackerTest, ShouldPopulateFaviconHashExplicitly) {
+  const std::string kSyncId = "SYNC_ID";
+  const std::string kFaviconPngBytes = "fakefaviconbytes";
+
+  std::unique_ptr<bookmarks::BookmarkModel> model =
+      bookmarks::TestBookmarkClient::CreateModel();
+
+  const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
+  const bookmarks::BookmarkNode* node =
+      model->AddURL(/*parent=*/bookmark_bar_node, /*index=*/0,
+                    base::ASCIIToUTF16("Title"), GURL("http://www.url.com"));
+
+  sync_pb::BookmarkModelMetadata model_metadata =
+      CreateMetadataForPermanentNodes(model.get());
+
+  // Add entry for the URL node.
+  *model_metadata.add_bookmarks_metadata() =
+      CreateNodeMetadata(node->id(), kSyncId);
+
+  std::unique_ptr<SyncedBookmarkTracker> tracker =
+      SyncedBookmarkTracker::CreateFromBookmarkModelAndMetadata(
+          model.get(), std::move(model_metadata));
+  ASSERT_THAT(tracker, NotNull());
+
+  const SyncedBookmarkTracker::Entity* entity =
+      tracker->GetEntityForSyncId(kSyncId);
+  ASSERT_THAT(entity, NotNull());
+  ASSERT_FALSE(entity->metadata()->has_bookmark_favicon_hash());
+  ASSERT_FALSE(entity->MatchesFaviconHash(kFaviconPngBytes));
+
+  tracker->PopulateFaviconHashIfUnset(entity, kFaviconPngBytes);
+  EXPECT_TRUE(entity->metadata()->has_bookmark_favicon_hash());
+  EXPECT_TRUE(entity->MatchesFaviconHash(kFaviconPngBytes));
+  EXPECT_FALSE(entity->MatchesFaviconHash("otherhash"));
+
+  // Further calls should be ignored.
+  tracker->PopulateFaviconHashIfUnset(entity, "otherpngbytes");
+  EXPECT_TRUE(entity->MatchesFaviconHash(kFaviconPngBytes));
+}
+
 }  // namespace
 
 }  // namespace sync_bookmarks
diff --git a/components/sync_sessions/BUILD.gn b/components/sync_sessions/BUILD.gn
index bef3d02..e28a293c 100644
--- a/components/sync_sessions/BUILD.gn
+++ b/components/sync_sessions/BUILD.gn
@@ -9,8 +9,6 @@
     "local_session_event_handler_impl.cc",
     "local_session_event_handler_impl.h",
     "local_session_event_router.h",
-    "lost_navigations_recorder.cc",
-    "lost_navigations_recorder.h",
     "open_tabs_ui_delegate.cc",
     "open_tabs_ui_delegate.h",
     "open_tabs_ui_delegate_impl.cc",
@@ -100,7 +98,6 @@
   sources = [
     "favicon_cache_unittest.cc",
     "local_session_event_handler_impl_unittest.cc",
-    "lost_navigations_recorder_unittest.cc",
     "open_tabs_ui_delegate_impl_unittest.cc",
     "session_store_unittest.cc",
     "session_sync_bridge_unittest.cc",
diff --git a/components/sync_sessions/lost_navigations_recorder.cc b/components/sync_sessions/lost_navigations_recorder.cc
deleted file mode 100644
index c3985540..0000000
--- a/components/sync_sessions/lost_navigations_recorder.cc
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync_sessions/lost_navigations_recorder.h"
-
-#include "base/metrics/histogram_macros.h"
-#include "base/stl_util.h"
-#include "components/sync/syncable/entry.h"
-
-namespace sync_sessions {
-
-LostNavigationsRecorder::LostNavigationsRecorder()
-    : recorder_state_(RECORDER_STATE_NOT_SYNCING) {}
-
-LostNavigationsRecorder::~LostNavigationsRecorder() {}
-
-void LostNavigationsRecorder::OnLocalChange(
-    const syncer::syncable::Entry* current_entry,
-    const syncer::SyncChange& change) {
-  if (change.sync_data().GetSpecifics().session().has_tab()) {
-    TransitionState(current_entry->GetSyncing(),
-                    current_entry->GetIsUnsynced());
-  }
-  RecordChange(change);
-}
-
-// Record a change either by adjusting our list of tabs or by recording the set
-// of navigations in the updated tab.
-void LostNavigationsRecorder::RecordChange(const syncer::SyncChange& change) {
-  sync_pb::SessionSpecifics session_specifics =
-      change.sync_data().GetSpecifics().session();
-  if (session_specifics.has_header()) {
-    DeleteTabs(session_specifics.header());
-    return;
-  } else if (!session_specifics.has_tab()) {
-    // There isn't any data we care about if neither a tab or window is
-    // modified.
-    return;
-  }
-  sync_pb::SessionTab tab = session_specifics.tab();
-  SessionID tab_id = SessionID::FromSerializedValue(tab.tab_id());
-
-  IdSet& latest = latest_navigation_ids_[tab_id];
-  latest.clear();
-
-  for (sync_pb::TabNavigation nav : tab.navigation()) {
-    id_type uid = nav.unique_id();
-    // Only record an id if it's "new" i.e. larger than the largest seen for
-    // that tab. If the id is smaller than this, it's not new; ids are generated
-    // in increasing order.
-    if (uid > max_recorded_for_tab_[tab_id]) {
-      recorded_navigation_ids_[tab_id].insert(uid);
-      max_recorded_for_tab_[tab_id] = uid;
-    }
-    latest.insert(nav.unique_id());
-  }
-}
-
-void LostNavigationsRecorder::DeleteTabs(const sync_pb::SessionHeader& header) {
-  std::set<SessionID> new_tab_ids;
-  std::set<SessionID> current_tab_ids;
-  // Find the set of tab ids that are still there after the deletion.
-  for (sync_pb::SessionWindow window : header.window()) {
-    for (auto tab_id : window.tab()) {
-      new_tab_ids.insert(SessionID::FromSerializedValue(tab_id));
-    }
-  }
-  for (auto pair : recorded_navigation_ids_) {
-    current_tab_ids.insert(pair.first);
-  }
-  // The set of deleted tabs is the difference between the set of tabs before
-  // the pending change and the set of tabs following the pending change.
-  auto deleted_tabs =
-      base::STLSetDifference<std::set<SessionID>>(current_tab_ids, new_tab_ids);
-  for (SessionID tab_id : deleted_tabs) {
-    recorded_navigation_ids_.erase(tab_id);
-    latest_navigation_ids_.erase(tab_id);
-    max_recorded_for_tab_.erase(tab_id);
-  }
-}
-
-// Change the current state of the recorder, possibly triggering reconciliation,
-// based on the status of the directory entry. Reconciliation triggers on the
-// observation of two conditions.
-// 1) The entry transitioning into the syncing state
-// 2) If we miss the transition to syncing state, the entry transitioning into
-// the synced state.
-void LostNavigationsRecorder::TransitionState(bool is_syncing,
-                                              bool is_unsynced) {
-  switch (recorder_state_) {
-    case RECORDER_STATE_NOT_SYNCING:
-      // If a sync cycle is happening or already finished, reconcile.
-      // It's possible that this will trigger reconciliation multiple times per
-      // sync cycle; once per tab that finishes the cycle in the synced state
-      // and the user performs a navigation in. In theory this will cause
-      // under-counting, since reconciliation clears each tab's remembered set
-      // of navigations. In practice the number of unique tabs written to in
-      // between two adjacent sync cycles should be pretty low,
-      // making the undercounting tolerable.
-      if (is_syncing || !is_unsynced) {
-        ReconcileLostNavs();
-      }
-      // If we're currently in a sync cycle, remember that.
-      if (is_syncing) {
-        recorder_state_ = RECORDER_STATE_SYNCING;
-      }
-      break;
-    case RECORDER_STATE_SYNCING:
-      if (!is_syncing) {
-        recorder_state_ = RECORDER_STATE_NOT_SYNCING;
-      }
-      break;
-  }
-}
-
-// Reconcile the number of "lost" navigations by checking all the unique ids we
-// recorded against what was actually synced.
-void LostNavigationsRecorder::ReconcileLostNavs() {
-  for (auto pair : recorded_navigation_ids_) {
-    SessionID tab_id = pair.first;
-    IdSet& latest = latest_navigation_ids_[tab_id];
-    IdSet& recorded = recorded_navigation_ids_[tab_id];
-    if (recorded.size() < 1) {
-      continue;
-    }
-
-    // The set of lost navigations is anything we recorded as new that's not
-    // present in latest.
-    IdSet lost_navs = base::STLSetDifference<IdSet>(recorded, latest);
-    int quantity_lost = lost_navs.size();
-    UMA_HISTOGRAM_COUNTS_1M("Sync.LostNavigationCount", quantity_lost);
-    recorded.clear();
-  }
-}
-}  // namespace sync_sessions
diff --git a/components/sync_sessions/lost_navigations_recorder.h b/components/sync_sessions/lost_navigations_recorder.h
deleted file mode 100644
index e3d5f91..0000000
--- a/components/sync_sessions/lost_navigations_recorder.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_SYNC_SESSIONS_LOST_NAVIGATIONS_RECORDER_H_
-#define COMPONENTS_SYNC_SESSIONS_LOST_NAVIGATIONS_RECORDER_H_
-
-#include <map>
-#include <set>
-
-#include "base/macros.h"
-#include "components/sessions/core/session_id.h"
-#include "components/sync/model/local_change_observer.h"
-#include "components/sync/model/sync_change.h"
-#include "components/sync/protocol/session_specifics.pb.h"
-
-namespace sync_sessions {
-
-// Recorder class that tracks the number of navigations written locally that
-// aren't synced. This is done by recording set of locally observed navigations
-// and  reconciling these sets against what was ultimately synced. These
-// navigations ultimately feed chrome history, so losing them prevents them from
-// being reflected in the history page.
-class LostNavigationsRecorder : public syncer::LocalChangeObserver {
- public:
-  using id_type = int32_t;
-  using IdSet = std::set<id_type>;
-  enum RecorderState { RECORDER_STATE_NOT_SYNCING, RECORDER_STATE_SYNCING };
-
-  LostNavigationsRecorder();
-  ~LostNavigationsRecorder() override;
-
-  // syncer::LocalChangeObserver implementation.
-  void OnLocalChange(const syncer::syncable::Entry* current_entry,
-                     const syncer::SyncChange& change) override;
-
- private:
-  void RecordChange(const syncer::SyncChange& change);
-  void DeleteTabs(const sync_pb::SessionHeader& header);
-  void TransitionState(bool is_syncing, bool is_unsynced);
-  void ReconcileLostNavs();
-
-  // State that records whether the most recently observed directory state was
-  // syncing or not syncing.
-  RecorderState recorder_state_;
-
-  // The set of all navigation ids we've observed for each tab_id since the last
-  // sync.
-  std::map<SessionID, IdSet> recorded_navigation_ids_;
-  // The set of navigation ids most recently recorded for each tab_id.
-  std::map<SessionID, IdSet> latest_navigation_ids_;
-  // The maximum navigation_id recorded for each tab_id.
-  std::map<SessionID, id_type> max_recorded_for_tab_;
-  DISALLOW_COPY_AND_ASSIGN(LostNavigationsRecorder);
-};
-}  // namespace sync_sessions
-
-#endif  // COMPONENTS_SYNC_SESSIONS_LOST_NAVIGATIONS_RECORDER_H_
diff --git a/components/sync_sessions/lost_navigations_recorder_unittest.cc b/components/sync_sessions/lost_navigations_recorder_unittest.cc
deleted file mode 100644
index 07033c3..0000000
--- a/components/sync_sessions/lost_navigations_recorder_unittest.cc
+++ /dev/null
@@ -1,396 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync_sessions/lost_navigations_recorder.h"
-
-#include <memory>
-#include <string>
-
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/task_environment.h"
-#include "components/sync/syncable/entry.h"
-#include "components/sync/syncable/mutable_entry.h"
-#include "components/sync/syncable/syncable_base_transaction.h"
-#include "components/sync/syncable/syncable_read_transaction.h"
-#include "components/sync/syncable/syncable_write_transaction.h"
-#include "components/sync/test/engine/test_directory_setter_upper.h"
-#include "components/sync/test/engine/test_id_factory.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using syncer::syncable::Entry;
-using syncer::syncable::Id;
-using syncer::syncable::MutableEntry;
-using syncer::syncable::WriteTransaction;
-
-namespace sync_sessions {
-namespace {
-
-const char kTab1SyncTag[] = "tab-YWRkcjHvv74=";
-const char kTab2SyncTag[] = "tab-2FyZDHvv74=";
-
-class LostNavigationsRecorderTest : public testing::Test {
- protected:
-  void SetUp() override {
-    dir_maker_.SetUp();
-    _id = 1;
-  }
-
-  void TearDown() override { dir_maker_.TearDown(); }
-
-  syncer::syncable::Directory* directory() { return dir_maker_.directory(); }
-
-  LostNavigationsRecorder* recorder() { return &recorder_; }
-
-  void AddNavigation(sync_pb::SessionSpecifics* tab_base,
-                     int id_override = -1) {
-    sync_pb::SessionTab* tab = tab_base->mutable_tab();
-    sync_pb::TabNavigation* navigation = tab->add_navigation();
-    navigation->set_page_transition(sync_pb::SyncEnums_PageTransition_TYPED);
-    navigation->set_unique_id(id_override > 0 ? id_override : _id++);
-  }
-
-  void BuildTabSpecifics(const std::string& tag,
-                         int tab_id,
-                         sync_pb::SessionSpecifics* tab_base,
-                         int num_unique_navs = 1) {
-    tab_base->set_session_tag(tag);
-    tab_base->set_tab_node_id(0);
-    sync_pb::SessionTab* tab = tab_base->mutable_tab();
-    tab->set_tab_id(tab_id);
-    tab->set_current_navigation_index(0);
-    for (int i = 0; i < num_unique_navs; ++i) {
-      AddNavigation(tab_base);
-    }
-  }
-
-  void BuildWindowSpecifics(int window_id,
-                            sync_pb::SessionSpecifics* window_base) {
-    sync_pb::SessionHeader* header = window_base->mutable_header();
-    sync_pb::SessionWindow* window = header->add_window();
-    window->set_window_id(window_id);
-  }
-
-  const Id& CreateEntry() {
-    WriteTransaction wtrans(FROM_HERE, syncer::syncable::UNITTEST, directory());
-    MutableEntry mutable_entry(&wtrans, syncer::syncable::CREATE,
-                               syncer::SESSIONS, wtrans.root_id(),
-                               "entrynamutable_entry");
-    EXPECT_TRUE(mutable_entry.good());
-    return mutable_entry.GetId();
-  }
-
-  const syncer::SyncData CreateLocalData(
-      const sync_pb::SessionSpecifics& specifics,
-      const std::string& sync_tag) const {
-    sync_pb::EntitySpecifics entity;
-    entity.mutable_session()->CopyFrom(specifics);
-    return syncer::SyncData::CreateLocalData(sync_tag, sync_tag, entity);
-  }
-
-  syncer::SyncChange MakeChange(const std::string& sync_tag,
-                                const sync_pb::SessionSpecifics& specifics,
-                                syncer::SyncChange::SyncChangeType type) const {
-    return syncer::SyncChange(FROM_HERE, type,
-                              CreateLocalData(specifics, sync_tag));
-  }
-
-  void RecordChange(const Entry* entry, int num_unique_navs) {
-    sync_pb::EntitySpecifics specifics;
-    BuildTabSpecifics(kTab1SyncTag, 1, specifics.mutable_session(),
-                      num_unique_navs);
-    RecordChange(entry, specifics);
-  }
-
-  void RecordChange(const Entry* entry, sync_pb::EntitySpecifics specifics) {
-    syncer::SyncChange change = MakeChange(kTab1SyncTag, specifics.session(),
-                                           syncer::SyncChange::ACTION_UPDATE);
-    recorder()->OnLocalChange(entry, change);
-  }
-
-  void TriggerReconcile(MutableEntry* mutable_entry,
-                        bool trigger_by_syncing = true,
-                        sync_pb::EntitySpecifics* specifics = nullptr) {
-    mutable_entry->PutSyncing(trigger_by_syncing);
-    mutable_entry->PutIsUnsynced(trigger_by_syncing);
-    if (specifics == nullptr) {
-      RecordChange(mutable_entry, 0);
-    } else {
-      RecordChange(mutable_entry, *specifics);
-    }
-  }
-
- private:
-  base::test::SingleThreadTaskEnvironment task_environment_;
-  int _id;
-  LostNavigationsRecorder recorder_;
-  syncer::TestDirectorySetterUpper dir_maker_;
-};
-
-TEST_F(LostNavigationsRecorderTest, MultipleNavsNoneLost) {
-  base::HistogramTester histogram_tester;
-  const Id& id = CreateEntry();
-  WriteTransaction wtrans(FROM_HERE, syncer::syncable::UNITTEST, directory());
-  MutableEntry mutable_entry(&wtrans, syncer::syncable::GET_BY_ID, id);
-  mutable_entry.PutIsUnsynced(true);
-
-  RecordChange(&mutable_entry, 6);
-
-  TriggerReconcile(&mutable_entry);
-  histogram_tester.ExpectBucketCount("Sync.LostNavigationCount", 0, 1);
-}
-
-TEST_F(LostNavigationsRecorderTest, MultipleNavsOneLost) {
-  base::HistogramTester histogram_tester;
-  const Id& id = CreateEntry();
-  WriteTransaction wtrans(FROM_HERE, syncer::syncable::UNITTEST, directory());
-  MutableEntry mutable_entry(&wtrans, syncer::syncable::GET_BY_ID, id);
-  mutable_entry.PutIsUnsynced(true);
-
-  RecordChange(&mutable_entry, 1);
-  RecordChange(&mutable_entry, 1);
-
-  TriggerReconcile(&mutable_entry);
-
-  histogram_tester.ExpectBucketCount("Sync.LostNavigationCount", 1, 1);
-}
-
-TEST_F(LostNavigationsRecorderTest, MultipleNavsMultipleLost) {
-  base::HistogramTester histogram_tester;
-  const Id& id = CreateEntry();
-  WriteTransaction wtrans(FROM_HERE, syncer::syncable::UNITTEST, directory());
-  MutableEntry mutable_entry(&wtrans, syncer::syncable::GET_BY_ID, id);
-  mutable_entry.PutIsUnsynced(true);
-
-  RecordChange(&mutable_entry, 5);
-  RecordChange(&mutable_entry, 1);
-
-  TriggerReconcile(&mutable_entry);
-  histogram_tester.ExpectBucketCount("Sync.LostNavigationCount", 5, 1);
-}
-
-TEST_F(LostNavigationsRecorderTest, MultipleWritesWhileSyncing) {
-  base::HistogramTester histogram_tester;
-  const Id& id = CreateEntry();
-  WriteTransaction wtrans(FROM_HERE, syncer::syncable::UNITTEST, directory());
-  MutableEntry mutable_entry(&wtrans, syncer::syncable::GET_BY_ID, id);
-
-  sync_pb::EntitySpecifics specifics;
-  BuildTabSpecifics(kTab1SyncTag, 1, specifics.mutable_session(), 5);
-  mutable_entry.PutIsUnsynced(true);
-  RecordChange(&mutable_entry, specifics);
-
-  mutable_entry.PutSyncing(true);
-  RecordChange(&mutable_entry, specifics);
-
-  specifics.mutable_session()->mutable_tab()->clear_navigation();
-  for (int i = 0; i < 5; i++) {
-    AddNavigation(specifics.mutable_session());
-    RecordChange(&mutable_entry, specifics);
-  }
-
-  TriggerReconcile(&mutable_entry, false);
-  histogram_tester.ExpectBucketCount("Sync.LostNavigationCount", 0, 1);
-}
-
-TEST_F(LostNavigationsRecorderTest, MultipleWritesMultipleEntriesNoneLost) {
-  base::HistogramTester histogram_tester;
-  const Id& id = CreateEntry();
-  const Id& id2 = CreateEntry();
-  WriteTransaction wtrans(FROM_HERE, syncer::syncable::UNITTEST, directory());
-  MutableEntry mutable_entry(&wtrans, syncer::syncable::GET_BY_ID, id);
-  MutableEntry mutable_entry2(&wtrans, syncer::syncable::GET_BY_ID, id2);
-  mutable_entry.PutIsUnsynced(true);
-  mutable_entry2.PutIsUnsynced(true);
-
-  sync_pb::EntitySpecifics specifics;
-  sync_pb::EntitySpecifics specifics2;
-  BuildTabSpecifics(kTab1SyncTag, 1, specifics.mutable_session(), 5);
-  BuildTabSpecifics(kTab2SyncTag, 2, specifics2.mutable_session(), 5);
-  RecordChange(&mutable_entry, specifics);
-  RecordChange(&mutable_entry2, specifics2);
-
-  mutable_entry.PutSyncing(true);
-  mutable_entry2.PutSyncing(true);
-  RecordChange(&mutable_entry, specifics);
-  RecordChange(&mutable_entry2, specifics2);
-
-  specifics.mutable_session()->mutable_tab()->clear_navigation();
-  specifics2.mutable_session()->mutable_tab()->clear_navigation();
-  for (int i = 0; i < 5; i++) {
-    AddNavigation(specifics.mutable_session());
-    AddNavigation(specifics2.mutable_session());
-
-    RecordChange(&mutable_entry, specifics);
-    RecordChange(&mutable_entry2, specifics2);
-  }
-
-  TriggerReconcile(&mutable_entry, false, &specifics);
-  TriggerReconcile(&mutable_entry2, false, &specifics2);
-
-  histogram_tester.ExpectBucketCount("Sync.LostNavigationCount", 0, 4);
-}
-
-TEST_F(LostNavigationsRecorderTest, MultipleWritesMultipleEntriesMultipleLost) {
-  base::HistogramTester histogram_tester;
-  const Id& id = CreateEntry();
-  const Id& id2 = CreateEntry();
-  WriteTransaction wtrans(FROM_HERE, syncer::syncable::UNITTEST, directory());
-  MutableEntry mutable_entry(&wtrans, syncer::syncable::GET_BY_ID, id);
-  MutableEntry mutable_entry2(&wtrans, syncer::syncable::GET_BY_ID, id2);
-  mutable_entry.PutIsUnsynced(true);
-  mutable_entry2.PutIsUnsynced(true);
-
-  sync_pb::EntitySpecifics specifics;
-  sync_pb::EntitySpecifics specifics2;
-  BuildTabSpecifics(kTab1SyncTag, 1, specifics.mutable_session(), 5);
-  BuildTabSpecifics(kTab2SyncTag, 2, specifics2.mutable_session(), 5);
-  RecordChange(&mutable_entry, specifics);
-  RecordChange(&mutable_entry2, specifics2);
-
-  mutable_entry.PutSyncing(true);
-  mutable_entry2.PutSyncing(true);
-  RecordChange(&mutable_entry, specifics);
-  RecordChange(&mutable_entry2, specifics2);
-
-  specifics.mutable_session()->mutable_tab()->clear_navigation();
-  specifics2.mutable_session()->mutable_tab()->clear_navigation();
-  for (int i = 0; i < 5; i++) {
-    AddNavigation(specifics.mutable_session());
-    AddNavigation(specifics2.mutable_session());
-
-    RecordChange(&mutable_entry, specifics);
-    RecordChange(&mutable_entry2, specifics2);
-  }
-
-  specifics.mutable_session()
-      ->mutable_tab()
-      ->mutable_navigation()
-      ->DeleteSubrange(0, 2);
-  specifics2.mutable_session()
-      ->mutable_tab()
-      ->mutable_navigation()
-      ->DeleteSubrange(0, 2);
-  RecordChange(&mutable_entry, specifics);
-  RecordChange(&mutable_entry, specifics2);
-
-  TriggerReconcile(&mutable_entry, false, &specifics);
-  TriggerReconcile(&mutable_entry2, false, &specifics2);
-
-  histogram_tester.ExpectBucketCount("Sync.LostNavigationCount", 2, 2);
-}
-
-TEST_F(LostNavigationsRecorderTest, NoWritesWhileSyncingMultipleLost) {
-  base::HistogramTester histogram_tester;
-  const Id& id = CreateEntry();
-  WriteTransaction wtrans(FROM_HERE, syncer::syncable::UNITTEST, directory());
-  MutableEntry mutable_entry(&wtrans, syncer::syncable::GET_BY_ID, id);
-
-  sync_pb::EntitySpecifics specifics;
-  BuildTabSpecifics(kTab1SyncTag, 1, specifics.mutable_session(), 5);
-  syncer::SyncChange change = MakeChange(kTab1SyncTag, specifics.session(),
-                                         syncer::SyncChange::ACTION_UPDATE);
-  mutable_entry.PutIsUnsynced(true);
-  recorder()->OnLocalChange(&mutable_entry, change);
-
-  specifics.mutable_session()->mutable_tab()->clear_navigation();
-  AddNavigation(specifics.mutable_session());
-  change = MakeChange(kTab1SyncTag, specifics.session(),
-                      syncer::SyncChange::ACTION_UPDATE);
-  recorder()->OnLocalChange(&mutable_entry, change);
-
-  mutable_entry.PutIsUnsynced(false);
-  recorder()->OnLocalChange(&mutable_entry, change);
-  histogram_tester.ExpectBucketCount("Sync.LostNavigationCount", 5, 1);
-}
-
-TEST_F(LostNavigationsRecorderTest, WindowChangeDoesNotTriggerReconcile) {
-  base::HistogramTester histogram_tester;
-  const Id& id = CreateEntry();
-  const Id& id2 = CreateEntry();
-  WriteTransaction wtrans(FROM_HERE, syncer::syncable::UNITTEST, directory());
-  MutableEntry mutable_entry(&wtrans, syncer::syncable::GET_BY_ID, id);
-  MutableEntry mutable_entry2(&wtrans, syncer::syncable::GET_BY_ID, id2);
-  mutable_entry.PutIsUnsynced(true);
-
-  RecordChange(&mutable_entry, 1);
-
-  sync_pb::EntitySpecifics specifics;
-  BuildWindowSpecifics(1, specifics.mutable_session());
-  RecordChange(&mutable_entry2, specifics);
-
-  EXPECT_EQ(0ul,
-            histogram_tester.GetAllSamples("Sync.LostNavigationCount").size());
-}
-
-TEST_F(LostNavigationsRecorderTest, Samutable_entryNavigationSetAcrossStates) {
-  base::HistogramTester histogram_tester;
-  const Id& id = CreateEntry();
-  WriteTransaction wtrans(FROM_HERE, syncer::syncable::UNITTEST, directory());
-  MutableEntry mutable_entry(&wtrans, syncer::syncable::GET_BY_ID, id);
-
-  sync_pb::EntitySpecifics specifics;
-  BuildTabSpecifics(kTab1SyncTag, 1, specifics.mutable_session(), 5);
-  syncer::SyncChange change = MakeChange(kTab1SyncTag, specifics.session(),
-                                         syncer::SyncChange::ACTION_UPDATE);
-  mutable_entry.PutIsUnsynced(true);
-  recorder()->OnLocalChange(&mutable_entry, change);
-  recorder()->OnLocalChange(&mutable_entry, change);
-
-  mutable_entry.PutIsUnsynced(false);
-  recorder()->OnLocalChange(&mutable_entry, change);
-  histogram_tester.ExpectBucketCount("Sync.LostNavigationCount", 0, 1);
-}
-
-TEST_F(LostNavigationsRecorderTest, RevisitPreviousNavs) {
-  base::HistogramTester histogram_tester;
-  const Id& id = CreateEntry();
-  WriteTransaction wtrans(FROM_HERE, syncer::syncable::UNITTEST, directory());
-  MutableEntry mutable_entry(&wtrans, syncer::syncable::GET_BY_ID, id);
-  mutable_entry.PutIsUnsynced(true);
-
-  sync_pb::EntitySpecifics specifics;
-  BuildTabSpecifics(kTab1SyncTag, 1, specifics.mutable_session(), 3);
-  RecordChange(&mutable_entry, specifics);
-
-  AddNavigation(specifics.mutable_session());
-  RecordChange(&mutable_entry, specifics);
-
-  specifics.mutable_session()
-      ->mutable_tab()
-      ->mutable_navigation()
-      ->RemoveLast();
-  RecordChange(&mutable_entry, specifics);
-
-  TriggerReconcile(&mutable_entry, true, &specifics);
-
-  histogram_tester.ExpectBucketCount("Sync.LostNavigationCount", 1, 1);
-}
-
-TEST_F(LostNavigationsRecorderTest, MultipleNavsMultipleLostWithOverlap) {
-  base::HistogramTester histogram_tester;
-  const Id& id = CreateEntry();
-  WriteTransaction wtrans(FROM_HERE, syncer::syncable::UNITTEST, directory());
-  MutableEntry mutable_entry(&wtrans, syncer::syncable::GET_BY_ID, id);
-  mutable_entry.PutIsUnsynced(true);
-
-  sync_pb::EntitySpecifics specifics;
-  BuildTabSpecifics(kTab1SyncTag, 1, specifics.mutable_session(), 5);
-  RecordChange(&mutable_entry, specifics);
-
-  AddNavigation(specifics.mutable_session());
-  AddNavigation(specifics.mutable_session());
-
-  specifics.mutable_session()
-      ->mutable_tab()
-      ->mutable_navigation()
-      ->DeleteSubrange(0, 2);
-  RecordChange(&mutable_entry, specifics);
-
-  TriggerReconcile(&mutable_entry, true, &specifics);
-
-  histogram_tester.ExpectBucketCount("Sync.LostNavigationCount", 2, 1);
-}
-
-}  // namespace
-}  // namespace sync_sessions
diff --git a/components/ukm/BUILD.gn b/components/ukm/BUILD.gn
index 6f687960b..768dd88 100644
--- a/components/ukm/BUILD.gn
+++ b/components/ukm/BUILD.gn
@@ -120,3 +120,19 @@
     "//components/test:run_all_unittests",
   ]
 }
+
+static_library("ukm_test_helper") {
+  testonly = true
+  sources = [
+    "ukm_test_helper.cc",
+    "ukm_test_helper.h",
+  ]
+  deps = [
+    ":ukm",
+    "//base",
+    "//components/metrics",
+    "//services/metrics/public/cpp:metrics_cpp",
+    "//third_party/metrics_proto",
+    "//third_party/zlib/google:compression_utils",
+  ]
+}
diff --git a/components/ukm/ukm_recorder_impl.h b/components/ukm/ukm_recorder_impl.h
index 7c8e27b..d0b2efb5 100644
--- a/components/ukm/ukm_recorder_impl.h
+++ b/components/ukm/ukm_recorder_impl.h
@@ -24,13 +24,13 @@
 
 namespace metrics {
 class UkmBrowserTestBase;
-class UkmEGTestHelper;
 }
 
 namespace ukm {
 class Report;
 class UkmRecorderImplTest;
 class UkmSource;
+class UkmTestHelper;
 class UkmUtilsForTest;
 
 namespace debug {
@@ -129,9 +129,9 @@
 
  private:
   friend ::metrics::UkmBrowserTestBase;
-  friend ::metrics::UkmEGTestHelper;
   friend ::ukm::debug::UkmDebugDataExtractor;
   friend ::ukm::UkmRecorderImplTest;
+  friend ::ukm::UkmTestHelper;
   friend ::ukm::UkmUtilsForTest;
   FRIEND_TEST_ALL_PREFIXES(UkmRecorderImplTest, IsSampledIn);
   FRIEND_TEST_ALL_PREFIXES(UkmRecorderImplTest, PurgeExtensionRecordings);
diff --git a/components/ukm/ukm_service.h b/components/ukm/ukm_service.h
index fb704952..e12b43b 100644
--- a/components/ukm/ukm_service.h
+++ b/components/ukm/ukm_service.h
@@ -30,12 +30,12 @@
 namespace metrics {
 class MetricsServiceClient;
 class UkmBrowserTestBase;
-class UkmEGTestHelper;
 class UkmDemographicMetricsProvider;
 }
 
 namespace ukm {
 class Report;
+class UkmTestHelper;
 
 namespace debug {
 class UkmDebugDataExtractor;
@@ -110,7 +110,7 @@
 
  private:
   friend ::metrics::UkmBrowserTestBase;
-  friend ::metrics::UkmEGTestHelper;
+  friend ::ukm::UkmTestHelper;
   friend ::ukm::debug::UkmDebugDataExtractor;
   friend ::ukm::UkmUtilsForTest;
   FRIEND_TEST_ALL_PREFIXES(::ChromeMetricsServiceClientTest,
diff --git a/components/ukm/ukm_test_helper.cc b/components/ukm/ukm_test_helper.cc
new file mode 100644
index 0000000..6ea053f
--- /dev/null
+++ b/components/ukm/ukm_test_helper.cc
@@ -0,0 +1,104 @@
+// 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 "components/ukm/ukm_test_helper.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/feature_list.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "components/metrics/unsent_log_store.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace ukm {
+
+UkmTestHelper::UkmTestHelper(UkmService* ukm_service)
+    : ukm_service_(ukm_service) {}
+
+bool UkmTestHelper::IsExtensionRecordingEnabled() const {
+  return ukm_service_ ? ukm_service_->extensions_enabled_ : false;
+}
+
+bool UkmTestHelper::IsRecordingEnabled() const {
+  return ukm_service_ ? ukm_service_->recording_enabled_ : false;
+}
+
+bool UkmTestHelper::IsReportUserNoisedUserBirthYearAndGenderEnabled() {
+  return base::FeatureList::IsEnabled(
+      ukm::UkmService::kReportUserNoisedUserBirthYearAndGender);
+}
+
+uint64_t UkmTestHelper::GetClientId() {
+  return ukm_service_->client_id_;
+}
+
+std::unique_ptr<Report> UkmTestHelper::GetUkmReport() {
+  if (!HasUnsentLogs())
+    return nullptr;
+
+  metrics::UnsentLogStore* log_store =
+      ukm_service_->reporting_service_.ukm_log_store();
+  if (log_store->has_staged_log()) {
+    // For testing purposes, we examine the content of a staged log without
+    // ever sending the log, so discard any previously staged log.
+    log_store->DiscardStagedLog();
+  }
+
+  log_store->StageNextLog();
+  if (!log_store->has_staged_log())
+    return nullptr;
+
+  std::string uncompressed_log_data;
+  if (!compression::GzipUncompress(log_store->staged_log(),
+                                   &uncompressed_log_data))
+    return nullptr;
+
+  std::unique_ptr<ukm::Report> report = std::make_unique<ukm::Report>();
+  if (!report->ParseFromString(uncompressed_log_data))
+    return nullptr;
+
+  return report;
+}
+
+UkmSource* UkmTestHelper::GetSource(SourceId source_id) {
+  if (!ukm_service_)
+    return nullptr;
+
+  auto it = ukm_service_->sources().find(source_id);
+  return it == ukm_service_->sources().end() ? nullptr : it->second.get();
+}
+
+bool UkmTestHelper::HasSource(SourceId source_id) {
+  return ukm_service_ && base::Contains(ukm_service_->sources(), source_id);
+}
+
+bool UkmTestHelper::IsSourceObsolete(SourceId source_id) {
+  return ukm_service_ &&
+         base::Contains(ukm_service_->recordings_.obsolete_source_ids,
+                        source_id);
+}
+
+void UkmTestHelper::RecordSourceForTesting(SourceId source_id) {
+  if (ukm_service_)
+    ukm_service_->UpdateSourceURL(source_id, GURL("http://example.com"));
+}
+
+void UkmTestHelper::BuildAndStoreLog() {
+  // Wait for initialization to complete before flushing.
+  base::RunLoop run_loop;
+  ukm_service_->SetInitializationCompleteCallbackForTesting(
+      run_loop.QuitClosure());
+  run_loop.Run();
+
+  ukm_service_->Flush();
+}
+
+bool UkmTestHelper::HasUnsentLogs() {
+  return ukm_service_ &&
+         ukm_service_->reporting_service_.ukm_log_store()->has_unsent_logs();
+}
+
+}  // namespace ukm
diff --git a/components/ukm/ukm_test_helper.h b/components/ukm/ukm_test_helper.h
new file mode 100644
index 0000000..4ad67bca
--- /dev/null
+++ b/components/ukm/ukm_test_helper.h
@@ -0,0 +1,67 @@
+// 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 COMPONENTS_UKM_UKM_TEST_HELPER_H_
+#define COMPONENTS_UKM_UKM_TEST_HELPER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "components/ukm/ukm_service.h"
+#include "services/metrics/public/cpp/ukm_source.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
+#include "third_party/metrics_proto/ukm/report.pb.h"
+
+namespace ukm {
+
+// Helper class that provides access to UkmService internals. This class is a
+// friend of UkmService and UkmRecorderImpl.
+class UkmTestHelper {
+ public:
+  explicit UkmTestHelper(UkmService* ukm_service);
+  ~UkmTestHelper() = default;
+
+  // Returns true if |ukm_service_| records extensions.
+  bool IsExtensionRecordingEnabled() const;
+
+  // Returns true if |ukm_service_| has recording enabled.
+  bool IsRecordingEnabled() const;
+
+  // Returns true if |ukm_service_| if the following feature is enabled:
+  // kReportUserNoisedUserBirthYearAndGender.
+  static bool IsReportUserNoisedUserBirthYearAndGenderEnabled();
+
+  // Returns |ukm_service_|'s client ID.
+  uint64_t GetClientId();
+
+  // Creates and returns a UKM report if there are unsent logs from which a
+  // report can be generated. Returns nullptr otherwise.
+  std::unique_ptr<Report> GetUkmReport();
+
+  // Returns the UkmSource corresponding to |source_id|, if present; otherwise,
+  // returns nullptr.
+  UkmSource* GetSource(SourceId source_id);
+
+  // Returns true if |ukm_service_| has a source corresponding to |source_id|.
+  bool HasSource(SourceId source_id);
+
+  // Returns true if UkmSource denoted by |source_id| is marked as obsolete.
+  bool IsSourceObsolete(SourceId source_id);
+
+  // Adds a dummy source with |source_id| to |ukm_service_|.
+  void RecordSourceForTesting(SourceId source_id);
+
+  // Creates a log and stores it in |ukm_service_|'s UnsentLogStore.
+  void BuildAndStoreLog();
+
+  // Reeturns true if |ukm_service_| has logs to send.
+  bool HasUnsentLogs();
+
+ private:
+  UkmService* const ukm_service_;
+};
+
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_UKM_TEST_HELPER_H_
diff --git a/components/viz/service/display/display_resource_provider.cc b/components/viz/service/display/display_resource_provider.cc
index 9958510..44d5d38 100644
--- a/components/viz/service/display/display_resource_provider.cc
+++ b/components/viz/service/display/display_resource_provider.cc
@@ -644,7 +644,9 @@
 
   std::vector<std::unique_ptr<ExternalUseClient::ImageContext>>
       image_contexts_to_return;
+  std::vector<ReturnedResource*> external_used_resources;
   image_contexts_to_return.reserve(unused.size());
+  external_used_resources.reserve(unused.size());
 
   GLES2Interface* gl = ContextGL();
   for (ResourceId local_id : unused) {
@@ -681,9 +683,6 @@
       is_lost = true;
     }
 
-    if (resource.image_context)
-      image_contexts_to_return.emplace_back(std::move(resource.image_context));
-
     if (resource.is_gpu_resource_type() &&
         resource.gl_id &&
         resource.filter != resource.transferable.filter) {
@@ -703,12 +702,20 @@
                            resource.imported_count, is_lost);
     auto& returned = to_return.back();
 
-    if (resource.is_gpu_resource_type()) {
-      if (resource.needs_sync_token()) {
-        need_synchronization_resources.push_back(&returned);
-      } else if (returned.sync_token.HasData() &&
-                 !returned.sync_token.verified_flush()) {
-        unverified_sync_tokens.push_back(returned.sync_token.GetData());
+    if (external_use_client_) {
+      if (resource.image_context) {
+        image_contexts_to_return.emplace_back(
+            std::move(resource.image_context));
+        external_used_resources.push_back(&returned);
+      }
+    } else {
+      if (resource.is_gpu_resource_type()) {
+        if (resource.needs_sync_token()) {
+          need_synchronization_resources.push_back(&returned);
+        } else if (returned.sync_token.HasData() &&
+                   !returned.sync_token.verified_flush()) {
+          unverified_sync_tokens.push_back(returned.sync_token.GetData());
+        }
       }
     }
 
@@ -720,28 +727,32 @@
     DeleteResourceInternal(it, style);
   }
 
-  gpu::SyncToken new_sync_token;
-  if (!need_synchronization_resources.empty()) {
-    DCHECK(gl);
-    gl->GenUnverifiedSyncTokenCHROMIUM(new_sync_token.GetData());
-    unverified_sync_tokens.push_back(new_sync_token.GetData());
+  if (external_use_client_) {
+    if (!image_contexts_to_return.empty()) {
+      gpu::SyncToken sync_token = external_use_client_->ReleaseImageContexts(
+          std::move(image_contexts_to_return));
+      for (auto* resource : external_used_resources) {
+        resource->sync_token = sync_token;
+      }
+    }
+  } else {
+    gpu::SyncToken new_sync_token;
+    if (!need_synchronization_resources.empty()) {
+      DCHECK(gl);
+      gl->GenUnverifiedSyncTokenCHROMIUM(new_sync_token.GetData());
+      unverified_sync_tokens.push_back(new_sync_token.GetData());
+    }
+
+    if (!unverified_sync_tokens.empty()) {
+      DCHECK(gl);
+      gl->VerifySyncTokensCHROMIUM(unverified_sync_tokens.data(),
+                                   unverified_sync_tokens.size());
+    }
+
+    // Set sync token after verification.
+    for (ReturnedResource* returned : need_synchronization_resources)
+      returned->sync_token = new_sync_token;
   }
-
-  if (!unverified_sync_tokens.empty()) {
-    DCHECK(gl);
-    gl->VerifySyncTokensCHROMIUM(unverified_sync_tokens.data(),
-                                 unverified_sync_tokens.size());
-  }
-
-  // Set sync token after verification.
-  for (ReturnedResource* returned : need_synchronization_resources)
-    returned->sync_token = new_sync_token;
-
-  if (external_use_client_ && !image_contexts_to_return.empty()) {
-    external_use_client_->ReleaseImageContexts(
-        std::move(image_contexts_to_return));
-  }
-
   if (!to_return.empty())
     child_info->return_callback.Run(to_return);
 
diff --git a/components/viz/service/display/display_resource_provider_unittest.cc b/components/viz/service/display/display_resource_provider_unittest.cc
index 577d60f0..390eeda 100644
--- a/components/viz/service/display/display_resource_provider_unittest.cc
+++ b/components/viz/service/display/display_resource_provider_unittest.cc
@@ -230,7 +230,8 @@
  public:
   MockExternalUseClient() = default;
   MOCK_METHOD1(ReleaseImageContexts,
-               void(std::vector<std::unique_ptr<ImageContext>> image_contexts));
+               gpu::SyncToken(
+                   std::vector<std::unique_ptr<ImageContext>> image_contexts));
   MOCK_METHOD5(CreateImageContext,
                std::unique_ptr<ImageContext>(
                    const gpu::MailboxHolder&,
@@ -307,15 +308,20 @@
                              0x456);
   sync_token2.SetVerifyFlush();
 
+  gpu::SyncToken sync_token3(gpu::CommandBufferNamespace::GPU_IO,
+                             gpu::CommandBufferId::FromUnsafeValue(0x234),
+                             0x567);
+  sync_token3.SetVerifyFlush();
   // We will get a second release of |parent_id| now that we've released our
   // external lock.
   EXPECT_CALL(client, ReleaseImageContexts(
-                          testing::ElementsAre(SamePtr(locked_image_context))));
+                          testing::ElementsAre(SamePtr(locked_image_context))))
+      .WillOnce(Return(sync_token3));
   // UnlockResources will also call DeclareUsedResourcesFromChild.
   lock_set.UnlockResources(sync_token2);
   // The resource should be returned after the lock is released.
   EXPECT_EQ(1u, returned_to_child.size());
-  EXPECT_EQ(sync_token2, returned_to_child[0].sync_token);
+  EXPECT_EQ(sync_token3, returned_to_child[0].sync_token);
   child_resource_provider_->ReceiveReturnsFromParent(returned_to_child);
   child_resource_provider_->RemoveImportedResource(id1);
 }
diff --git a/components/viz/service/display/external_use_client.h b/components/viz/service/display/external_use_client.h
index bfb95f8..95d798c 100644
--- a/components/viz/service/display/external_use_client.h
+++ b/components/viz/service/display/external_use_client.h
@@ -100,7 +100,7 @@
       const base::Optional<gpu::VulkanYCbCrInfo>& ycbcr_info,
       sk_sp<SkColorSpace> color_space) = 0;
 
-  virtual void ReleaseImageContexts(
+  virtual gpu::SyncToken ReleaseImageContexts(
       std::vector<std::unique_ptr<ImageContext>> image_contexts) = 0;
 };
 
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc
index 8c0ea3f..f602ec2 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -356,17 +356,24 @@
   return image;
 }
 
-void SkiaOutputSurfaceImpl::ReleaseImageContexts(
+gpu::SyncToken SkiaOutputSurfaceImpl::ReleaseImageContexts(
     std::vector<std::unique_ptr<ImageContext>> image_contexts) {
   if (image_contexts.empty())
-    return;
+    return gpu::SyncToken();
+
+  gpu::SyncToken sync_token(
+      gpu::CommandBufferNamespace::VIZ_SKIA_OUTPUT_SURFACE,
+      impl_on_gpu_->command_buffer_id(), ++sync_fence_release_);
+  sync_token.SetVerifyFlush();
 
   // impl_on_gpu_ is released on the GPU thread by a posted task from
   // SkiaOutputSurfaceImpl::dtor. So it is safe to use base::Unretained.
-  auto callback = base::BindOnce(
-      &SkiaOutputSurfaceImplOnGpu::ReleaseImageContexts,
-      base::Unretained(impl_on_gpu_.get()), std::move(image_contexts));
+  auto callback =
+      base::BindOnce(&SkiaOutputSurfaceImplOnGpu::ReleaseImageContexts,
+                     base::Unretained(impl_on_gpu_.get()),
+                     std::move(image_contexts), sync_fence_release_);
   gpu_task_scheduler_->ScheduleOrRetainGpuTask(std::move(callback), {});
+  return sync_token;
 }
 
 std::unique_ptr<ExternalUseClient::ImageContext>
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.h b/components/viz/service/display_embedder/skia_output_surface_impl.h
index 187a383..8c4dbe0 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.h
@@ -129,7 +129,7 @@
   void RemoveContextLostObserver(ContextLostObserver* observer) override;
 
   // ExternalUseClient implementation:
-  void ReleaseImageContexts(
+  gpu::SyncToken ReleaseImageContexts(
       std::vector<std::unique_ptr<ImageContext>> image_contexts) override;
   std::unique_ptr<ExternalUseClient::ImageContext> CreateImageContext(
       const gpu::MailboxHolder& holder,
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index 60be0aa..fd5e7c5 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -1404,7 +1404,8 @@
 
 void SkiaOutputSurfaceImplOnGpu::ReleaseImageContexts(
     std::vector<std::unique_ptr<ExternalUseClient::ImageContext>>
-        image_contexts) {
+        image_contexts,
+    uint64_t sync_fence_release) {
   DCHECK(!image_contexts.empty());
   // The window could be destroyed already, and the MakeCurrent will fail with
   // an destroyed window, so MakeCurrent without requiring the fbo0.
@@ -1413,7 +1414,8 @@
       context->OnContextLost();
   }
 
-  // |image_contexts| goes out of scope here.
+  image_contexts.clear();
+  ReleaseFenceSyncAndPushTextureUpdates(sync_fence_release);
 }
 
 void SkiaOutputSurfaceImplOnGpu::ScheduleOverlays(
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
index 47b823a..a482dd6 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
@@ -169,7 +169,8 @@
   size_t max_resource_cache_bytes() const { return max_resource_cache_bytes_; }
   void ReleaseImageContexts(
       std::vector<std::unique_ptr<ExternalUseClient::ImageContext>>
-          image_contexts);
+          image_contexts,
+      uint64_t sync_fence_release);
   void ScheduleOverlays(SkiaOutputSurface::OverlayList overlays);
 
 #if defined(OS_WIN)
diff --git a/components/viz/test/fake_skia_output_surface.cc b/components/viz/test/fake_skia_output_surface.cc
index 433c5cfd..7265a00f 100644
--- a/components/viz/test/fake_skia_output_surface.cc
+++ b/components/viz/test/fake_skia_output_surface.cc
@@ -171,8 +171,10 @@
   return nullptr;
 }
 
-void FakeSkiaOutputSurface::ReleaseImageContexts(
-    std::vector<std::unique_ptr<ImageContext>> image_contexts) {}
+gpu::SyncToken FakeSkiaOutputSurface::ReleaseImageContexts(
+    std::vector<std::unique_ptr<ImageContext>> image_contexts) {
+  return gpu::SyncToken();
+}
 
 std::unique_ptr<ExternalUseClient::ImageContext>
 FakeSkiaOutputSurface::CreateImageContext(
diff --git a/components/viz/test/fake_skia_output_surface.h b/components/viz/test/fake_skia_output_surface.h
index 5f37a1d..39745b6 100644
--- a/components/viz/test/fake_skia_output_surface.h
+++ b/components/viz/test/fake_skia_output_surface.h
@@ -100,7 +100,7 @@
   void RemoveContextLostObserver(ContextLostObserver* observer) override;
 
   // ExternalUseClient implementation:
-  void ReleaseImageContexts(
+  gpu::SyncToken ReleaseImageContexts(
       const std::vector<std::unique_ptr<ImageContext>> image_contexts) override;
   std::unique_ptr<ImageContext> CreateImageContext(
       const gpu::MailboxHolder& holder,
diff --git a/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc b/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
index 35563fa..6875a7a6f 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
@@ -27,7 +27,7 @@
 namespace content {
 
 class AccessibilityTreeFormatterAuraLinux
-    : public AccessibilityTreeFormatterBrowser {
+    : public AccessibilityTreeFormatterBase {
  public:
   AccessibilityTreeFormatterAuraLinux();
   ~AccessibilityTreeFormatterAuraLinux() override;
@@ -38,13 +38,13 @@
   const std::string GetAllowString() override;
   const std::string GetDenyString() override;
   const std::string GetDenyNodeString() override;
-  void AddProperties(const BrowserAccessibility& node,
-                     base::DictionaryValue* dict) override;
 
   base::string16 ProcessTreeForOutput(
       const base::DictionaryValue& node,
       base::DictionaryValue* filtered_dict_result = nullptr) override;
 
+  std::unique_ptr<base::DictionaryValue> BuildAccessibilityTree(
+      BrowserAccessibility* root) override;
   std::unique_ptr<base::DictionaryValue> BuildAccessibilityTreeForProcess(
       base::ProcessId pid) override;
   std::unique_ptr<base::DictionaryValue> BuildAccessibilityTreeForWindow(
@@ -54,6 +54,13 @@
   std::unique_ptr<base::DictionaryValue> BuildAccessibilityTreeWithNode(
       AtspiAccessible* node);
 
+  void RecursiveBuildAccessibilityTree(AtspiAccessible* node,
+                                       base::DictionaryValue* dict);
+  void RecursiveBuildAccessibilityTree(AtkObject*, base::DictionaryValue*);
+
+  void AddProperties(AtkObject*, base::DictionaryValue*);
+  void AddProperties(AtspiAccessible*, base::DictionaryValue*);
+
   void AddTextProperties(AtkText* atk_text, base::DictionaryValue* dict);
   void AddActionProperties(AtkObject* atk_object, base::DictionaryValue* dict);
   void AddValueProperties(AtkObject* atk_object, base::DictionaryValue* dict);
@@ -61,11 +68,6 @@
   void AddTableCellProperties(const ui::AXPlatformNodeAuraLinux* node,
                               AtkObject* atk_object,
                               base::DictionaryValue* dict);
-
-  void RecursiveBuildAccessibilityTree(AtspiAccessible* node,
-                                       base::DictionaryValue* dict);
-  virtual void AddProperties(AtspiAccessible* node,
-                             base::DictionaryValue* dict);
 };
 
 // static
@@ -136,6 +138,24 @@
 }
 
 std::unique_ptr<base::DictionaryValue>
+AccessibilityTreeFormatterAuraLinux::BuildAccessibilityTree(
+    BrowserAccessibility* root) {
+  DCHECK(root);
+  DCHECK(root->instance_active());
+
+  BrowserAccessibilityAuraLinux* platform_root =
+      ToBrowserAccessibilityAuraLinux(root);
+  DCHECK(platform_root);
+
+  AtkObject* atk_root = platform_root->GetNativeViewAccessible();
+  DCHECK(atk_root);
+
+  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+  RecursiveBuildAccessibilityTree(atk_root, dict.get());
+  return dict;
+}
+
+std::unique_ptr<base::DictionaryValue>
 AccessibilityTreeFormatterAuraLinux::BuildAccessibilityTreeForProcess(
     base::ProcessId pid) {
   LOG(ERROR) << "Aura Linux does not yet support building trees for processes";
@@ -163,6 +183,32 @@
 }
 
 void AccessibilityTreeFormatterAuraLinux::RecursiveBuildAccessibilityTree(
+    AtkObject* atk_node,
+    base::DictionaryValue* dict) {
+  AddProperties(atk_node, dict);
+
+  auto child_count = atk_object_get_n_accessible_children(atk_node);
+  if (child_count <= 0)
+    return;
+
+  auto children = std::make_unique<base::ListValue>();
+  for (auto i = 0; i < child_count; i++) {
+    std::unique_ptr<base::DictionaryValue> child_dict(
+        new base::DictionaryValue);
+
+    AtkObject* atk_child = atk_object_ref_accessible_child(atk_node, i);
+    CHECK(atk_child);
+
+    RecursiveBuildAccessibilityTree(atk_child, child_dict.get());
+    g_object_unref(atk_child);
+
+    children->Append(std::move(child_dict));
+  }
+
+  dict->Set(kChildrenDictAttr, std::move(children));
+}
+
+void AccessibilityTreeFormatterAuraLinux::RecursiveBuildAccessibilityTree(
     AtspiAccessible* node,
     base::DictionaryValue* dict) {
   AddProperties(node, dict);
@@ -412,18 +458,17 @@
 }
 
 void AccessibilityTreeFormatterAuraLinux::AddProperties(
-    const BrowserAccessibility& node,
+    AtkObject* atk_object,
     base::DictionaryValue* dict) {
-  dict->SetInteger("id", node.GetId());
-  BrowserAccessibilityAuraLinux* acc_obj =
-      ToBrowserAccessibilityAuraLinux(const_cast<BrowserAccessibility*>(&node));
-  DCHECK(acc_obj);
+  ui::AXPlatformNodeAuraLinux* platform_node =
+      ui::AXPlatformNodeAuraLinux::FromAtkObject(atk_object);
+  DCHECK(platform_node);
 
-  ui::AXPlatformNodeAuraLinux* ax_platform_node = acc_obj->GetNode();
-  DCHECK(ax_platform_node);
+  BrowserAccessibility* node = BrowserAccessibility::FromAXPlatformNodeDelegate(
+      platform_node->GetDelegate());
+  DCHECK(node);
 
-  AtkObject* atk_object = ax_platform_node->GetNativeViewAccessible();
-  DCHECK(atk_object);
+  dict->SetInteger("id", node->GetId());
 
   AtkRole role = atk_object_get_role(atk_object);
   if (role != ATK_ROLE_UNKNOWN) {
@@ -469,7 +514,7 @@
   AddActionProperties(atk_object, dict);
   AddValueProperties(atk_object, dict);
   AddTableProperties(atk_object, dict);
-  AddTableCellProperties(ax_platform_node, atk_object, dict);
+  AddTableCellProperties(platform_node, atk_object, dict);
 }
 
 void AccessibilityTreeFormatterAuraLinux::AddProperties(
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index 457061c1..b7c30ad 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -229,10 +229,6 @@
   return container;
 }
 
-bool BrowserAccessibility::IsNative() const {
-  return false;
-}
-
 bool BrowserAccessibility::IsDescendantOf(
     const BrowserAccessibility* ancestor) const {
   if (!ancestor)
diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h
index cbddbe6..f64af53 100644
--- a/content/browser/accessibility/browser_accessibility.h
+++ b/content/browser/accessibility/browser_accessibility.h
@@ -323,11 +323,6 @@
   typedef base::StringPairs HtmlAttributes;
   const HtmlAttributes& GetHtmlAttributes() const;
 
-  // Returns true if this is a native platform-specific object, vs a
-  // cross-platform generic object. Don't call ToBrowserAccessibilityXXX if
-  // IsNative returns false.
-  virtual bool IsNative() const;
-
   // Accessing accessibility attributes:
   //
   // There are dozens of possible attributes for an accessibility node,
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index c12d9effd..e4acbee 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -87,10 +87,6 @@
     g_unique_id_map.Get().erase(unique_id());
 }
 
-bool BrowserAccessibilityAndroid::IsNative() const {
-  return true;
-}
-
 void BrowserAccessibilityAndroid::OnLocationChanged() {
   auto* manager =
       static_cast<BrowserAccessibilityManagerAndroid*>(this->manager());
diff --git a/content/browser/accessibility/browser_accessibility_android.h b/content/browser/accessibility/browser_accessibility_android.h
index bfccc065..ed8924e9 100644
--- a/content/browser/accessibility/browser_accessibility_android.h
+++ b/content/browser/accessibility/browser_accessibility_android.h
@@ -26,7 +26,6 @@
 
   // Overrides from BrowserAccessibility.
   void OnDataChanged() override;
-  bool IsNative() const override;
   void OnLocationChanged() override;
   base::string16 GetValue() const override;
 
diff --git a/content/browser/accessibility/browser_accessibility_auralinux.cc b/content/browser/accessibility/browser_accessibility_auralinux.cc
index d666a20..23fa1b0 100644
--- a/content/browser/accessibility/browser_accessibility_auralinux.cc
+++ b/content/browser/accessibility/browser_accessibility_auralinux.cc
@@ -11,7 +11,6 @@
 
 BrowserAccessibilityAuraLinux* ToBrowserAccessibilityAuraLinux(
     BrowserAccessibility* obj) {
-  DCHECK(!obj || obj->IsNative());
   return static_cast<BrowserAccessibilityAuraLinux*>(obj);
 }
 
@@ -57,10 +56,6 @@
   return GetNode();
 }
 
-bool BrowserAccessibilityAuraLinux::IsNative() const {
-  return true;
-}
-
 base::string16 BrowserAccessibilityAuraLinux::GetText() const {
   return GetHypertext();
 }
diff --git a/content/browser/accessibility/browser_accessibility_auralinux.h b/content/browser/accessibility/browser_accessibility_auralinux.h
index 8f23f51..6667aaf 100644
--- a/content/browser/accessibility/browser_accessibility_auralinux.h
+++ b/content/browser/accessibility/browser_accessibility_auralinux.h
@@ -31,7 +31,6 @@
   // BrowserAccessibility methods.
   void OnDataChanged() override;
   ui::AXPlatformNode* GetAXPlatformNode() const override;
-  bool IsNative() const override;
   base::string16 GetText() const override;
   base::string16 GetHypertext() const override;
 
diff --git a/content/browser/accessibility/browser_accessibility_com_win.cc b/content/browser/accessibility/browser_accessibility_com_win.cc
index c81bcbc8..0066bad4 100644
--- a/content/browser/accessibility/browser_accessibility_com_win.cc
+++ b/content/browser/accessibility/browser_accessibility_com_win.cc
@@ -1677,7 +1677,7 @@
 
 BrowserAccessibilityComWin* ToBrowserAccessibilityComWin(
     BrowserAccessibility* obj) {
-  if (!obj || !obj->IsNative())
+  if (!obj)
     return nullptr;
   auto* result = static_cast<BrowserAccessibilityWin*>(obj)->GetCOM();
   return result;
diff --git a/content/browser/accessibility/browser_accessibility_mac.h b/content/browser/accessibility/browser_accessibility_mac.h
index 9b14ad2b3..e5358e5 100644
--- a/content/browser/accessibility/browser_accessibility_mac.h
+++ b/content/browser/accessibility/browser_accessibility_mac.h
@@ -28,7 +28,6 @@
  public:
   // BrowserAccessibility overrides.
   ~BrowserAccessibilityMac() override;
-  bool IsNative() const override;
   void OnDataChanged() override;
   uint32_t PlatformChildCount() const override;
   BrowserAccessibility* PlatformGetChild(uint32_t child_index) const override;
diff --git a/content/browser/accessibility/browser_accessibility_mac.mm b/content/browser/accessibility/browser_accessibility_mac.mm
index 92fafed8..1f160cf 100644
--- a/content/browser/accessibility/browser_accessibility_mac.mm
+++ b/content/browser/accessibility/browser_accessibility_mac.mm
@@ -31,10 +31,6 @@
   [browser_accessibility_cocoa_ release];
 }
 
-bool BrowserAccessibilityMac::IsNative() const {
-  return true;
-}
-
 void BrowserAccessibilityMac::OnDataChanged() {
   BrowserAccessibility::OnDataChanged();
 
@@ -180,14 +176,12 @@
 const BrowserAccessibilityCocoa* ToBrowserAccessibilityCocoa(
     const BrowserAccessibility* obj) {
   DCHECK(obj);
-  DCHECK(obj->IsNative());
   return static_cast<const BrowserAccessibilityMac*>(obj)->native_view();
 }
 
 BrowserAccessibilityCocoa* ToBrowserAccessibilityCocoa(
     BrowserAccessibility* obj) {
   DCHECK(obj);
-  DCHECK(obj->IsNative());
   return static_cast<BrowserAccessibilityMac*>(obj)->native_view();
 }
 
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index 5a7f44a..e29f4cec 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -1534,7 +1534,7 @@
     DCHECK(changed_node);
 
     BrowserAccessibility* obj = GetFromAXNode(changed_node);
-    if (obj && obj->IsNative())
+    if (obj)
       nodes_needing_update->insert(obj->GetAXPlatformNode());
 
     // When a node is a text node or line break, update its parent, because
@@ -1545,7 +1545,7 @@
 
     if (ui::IsTextOrLineBreak(changed_node->data().role)) {
       BrowserAccessibility* parent_obj = GetFromAXNode(parent);
-      if (parent_obj && parent_obj->IsNative())
+      if (parent_obj)
         nodes_needing_update->insert(parent_obj->GetAXPlatformNode());
     }
 
@@ -1559,7 +1559,7 @@
     }
 
     BrowserAccessibility* editable_root_obj = GetFromAXNode(editable_root);
-    if (editable_root_obj && editable_root_obj->IsNative())
+    if (editable_root_obj)
       nodes_needing_update->insert(editable_root_obj->GetAXPlatformNode());
   }
 }
diff --git a/content/browser/accessibility/browser_accessibility_manager_auralinux.cc b/content/browser/accessibility/browser_accessibility_manager_auralinux.cc
index 62ecc196..7b75997f 100644
--- a/content/browser/accessibility/browser_accessibility_manager_auralinux.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_auralinux.cc
@@ -69,9 +69,6 @@
 void BrowserAccessibilityManagerAuraLinux::FireLoadingEvent(
     BrowserAccessibility* node,
     bool is_loading) {
-  if (!node->IsNative())
-    return;
-
   gfx::NativeViewAccessible obj = node->GetNativeViewAccessible();
   if (!ATK_IS_OBJECT(obj))
     return;
@@ -84,18 +81,12 @@
 void BrowserAccessibilityManagerAuraLinux::FireExpandedEvent(
     BrowserAccessibility* node,
     bool is_expanded) {
-  if (!node->IsNative())
-    return;
-
   ToBrowserAccessibilityAuraLinux(node)->GetNode()->OnExpandedStateChanged(
       is_expanded);
 }
 
 void BrowserAccessibilityManagerAuraLinux::FireEvent(BrowserAccessibility* node,
                                                      ax::mojom::Event event) {
-  if (!node->IsNative())
-    return;
-
   ToBrowserAccessibilityAuraLinux(node)->GetNode()->NotifyAccessibilityEvent(
       event);
 }
@@ -205,7 +196,7 @@
   // children-changed:add is handled with the generated Event::IGNORED_CHANGED.
   if (!old_node_data.IsIgnored() && new_node_data.IsIgnored()) {
     BrowserAccessibility* obj = GetFromID(old_node_data.id);
-    if (obj && obj->IsNative() && obj->GetParent()) {
+    if (obj && obj->GetParent()) {
       DCHECK(!obj->IsIgnored());
       g_signal_emit_by_name(obj->GetParent(), "children-changed::remove",
                             obj->GetIndexInParent(),
@@ -222,7 +213,7 @@
     return;
 
   BrowserAccessibility* obj = GetFromAXNode(node);
-  if (obj && obj->IsNative())
+  if (obj)
     ToBrowserAccessibilityAuraLinux(obj)->GetNode()->OnSubtreeWillBeDeleted();
 }
 
diff --git a/content/browser/accessibility/browser_accessibility_manager_mac.mm b/content/browser/accessibility/browser_accessibility_manager_mac.mm
index ebe4e42..9a9f6dea 100644
--- a/content/browser/accessibility/browser_accessibility_manager_mac.mm
+++ b/content/browser/accessibility/browser_accessibility_manager_mac.mm
@@ -203,9 +203,6 @@
     ui::AXEventGenerator::Event event_type,
     BrowserAccessibility* node) {
   BrowserAccessibilityManager::FireGeneratedEvent(event_type, node);
-  if (!node->IsNative())
-    return;
-
   auto native_node = ToBrowserAccessibilityCocoa(node);
   DCHECK(native_node);
 
@@ -469,9 +466,6 @@
 void BrowserAccessibilityManagerMac::FireNativeMacNotification(
     NSString* mac_notification,
     BrowserAccessibility* node) {
-  if (!node->IsNative())
-    return;
-
   DCHECK(mac_notification);
   auto native_node = ToBrowserAccessibilityCocoa(node);
   DCHECK(native_node);
@@ -494,7 +488,7 @@
   std::set<const BrowserAccessibilityCocoa*> changed_editable_roots;
   for (const auto& change : changes) {
     const BrowserAccessibility* obj = GetFromAXNode(change.node);
-    if (obj && obj->IsNative() && obj->HasState(ax::mojom::State::kEditable)) {
+    if (obj && obj->HasState(ax::mojom::State::kEditable)) {
       const BrowserAccessibilityCocoa* editable_root =
           [ToBrowserAccessibilityCocoa(obj) editableAncestor];
       if (editable_root && [editable_root instanceActive])
diff --git a/content/browser/accessibility/browser_accessibility_win.cc b/content/browser/accessibility/browser_accessibility_win.cc
index 5124398..ae9e40f 100644
--- a/content/browser/accessibility/browser_accessibility_win.cc
+++ b/content/browser/accessibility/browser_accessibility_win.cc
@@ -47,10 +47,6 @@
   return GetCOM();
 }
 
-bool BrowserAccessibilityWin::IsNative() const {
-  return true;
-}
-
 void BrowserAccessibilityWin::OnLocationChanged() {
   GetCOM()->FireNativeEvent(EVENT_OBJECT_LOCATIONCHANGE);
 }
@@ -104,13 +100,11 @@
 }
 
 BrowserAccessibilityWin* ToBrowserAccessibilityWin(BrowserAccessibility* obj) {
-  DCHECK(!obj || obj->IsNative());
   return static_cast<BrowserAccessibilityWin*>(obj);
 }
 
 const BrowserAccessibilityWin* ToBrowserAccessibilityWin(
     const BrowserAccessibility* obj) {
-  DCHECK(!obj || obj->IsNative());
   return static_cast<const BrowserAccessibilityWin*>(obj);
 }
 
diff --git a/content/browser/accessibility/browser_accessibility_win.h b/content/browser/accessibility/browser_accessibility_win.h
index 6087e0f..70e2fd3 100644
--- a/content/browser/accessibility/browser_accessibility_win.h
+++ b/content/browser/accessibility/browser_accessibility_win.h
@@ -26,7 +26,6 @@
   // BrowserAccessibility methods.
   //
   ui::AXPlatformNode* GetAXPlatformNode() const override;
-  bool IsNative() const override;
   void OnLocationChanged() override;
   base::string16 GetText() const override;
   base::string16 GetHypertext() const override;
diff --git a/content/browser/cache_storage/cache_storage_cache_unittest.cc b/content/browser/cache_storage/cache_storage_cache_unittest.cc
index f94877a..143d940 100644
--- a/content/browser/cache_storage/cache_storage_cache_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_cache_unittest.cc
@@ -677,6 +677,7 @@
         nullptr /* side_data_blob */,
         nullptr /* side_data_blob_for_cache_put */,
         std::vector<network::mojom::ContentSecurityPolicyPtr>(),
+        network::CrossOriginEmbedderPolicy(),
         false /* loaded_with_credentials */);
   }
 
diff --git a/content/browser/cache_storage/cache_storage_manager_unittest.cc b/content/browser/cache_storage/cache_storage_manager_unittest.cc
index 485b989f..ef8c9842 100644
--- a/content/browser/cache_storage/cache_storage_manager_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_manager_unittest.cc
@@ -669,6 +669,7 @@
         nullptr /* side_data_blob */,
         nullptr /* side_data_blob_for_cache_put */,
         std::vector<network::mojom::ContentSecurityPolicyPtr>(),
+        network::CrossOriginEmbedderPolicy(),
         false /* loaded_with_credentials */);
 
     blink::mojom::BatchOperationPtr operation =
diff --git a/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc b/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc
index fe1ac2e..6321c5b 100644
--- a/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc
+++ b/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc
@@ -358,6 +358,7 @@
           metadata.response().cors_exposed_header_names().end()),
       nullptr /* side_data_blob */, nullptr /* side_data_blob_for_cache_put */,
       std::vector<network::mojom::ContentSecurityPolicyPtr>(),
+      network::CrossOriginEmbedderPolicy(),
       metadata.response().loaded_with_credentials());
 }
 
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index c4fe5c00..3e00cb2 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -176,7 +176,6 @@
 #include "media/learning/common/value.h"
 #include "media/media_buildflags.h"
 #include "media/mojo/mojom/remoting.mojom.h"
-#include "media/mojo/services/media_interface_provider.h"
 #include "media/mojo/services/video_decode_perf_history.h"
 #include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
@@ -6966,7 +6965,8 @@
     mojo::ReportBadMessage("Must have the same origin as the top-level frame.");
     return;
   }
-  auto* fetcher = SmsFetcher::Get(GetProcess()->GetBrowserContext(), this);
+  auto* fetcher = SmsFetcher::Get(GetProcess()->GetBrowserContext(),
+                                  weak_ptr_factory_.GetWeakPtr());
   SmsService::Create(fetcher, this, std::move(receiver));
 }
 
diff --git a/content/browser/media/media_interface_factory_holder.cc b/content/browser/media/media_interface_factory_holder.cc
index d4c9aeb..dcba3df9 100644
--- a/content/browser/media/media_interface_factory_holder.cc
+++ b/content/browser/media/media_interface_factory_holder.cc
@@ -8,9 +8,9 @@
 
 MediaInterfaceFactoryHolder::MediaInterfaceFactoryHolder(
     MediaServiceGetter media_service_getter,
-    CreateInterfaceProviderCB create_interface_provider_cb)
+    FrameServicesGetter frame_services_getter)
     : media_service_getter_(std::move(media_service_getter)),
-      create_interface_provider_cb_(std::move(create_interface_provider_cb)) {}
+      frame_services_getter_(std::move(frame_services_getter)) {}
 
 MediaInterfaceFactoryHolder::~MediaInterfaceFactoryHolder() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -28,7 +28,7 @@
 void MediaInterfaceFactoryHolder::ConnectToMediaService() {
   media_service_getter_.Run().CreateInterfaceFactory(
       interface_factory_remote_.BindNewPipeAndPassReceiver(),
-      create_interface_provider_cb_.Run());
+      frame_services_getter_.Run());
   interface_factory_remote_.set_disconnect_handler(base::BindOnce(
       &MediaInterfaceFactoryHolder::OnMediaServiceConnectionError,
       base::Unretained(this)));
diff --git a/content/browser/media/media_interface_factory_holder.h b/content/browser/media/media_interface_factory_holder.h
index f77e02e..3325c2b 100644
--- a/content/browser/media/media_interface_factory_holder.h
+++ b/content/browser/media/media_interface_factory_holder.h
@@ -10,11 +10,11 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/threading/thread_checker.h"
+#include "media/mojo/mojom/frame_interface_factory.mojom.h"
 #include "media/mojo/mojom/interface_factory.mojom.h"
 #include "media/mojo/mojom/media_service.mojom.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "services/service_manager/public/mojom/interface_provider.mojom.h"
 
 namespace content {
 
@@ -24,13 +24,12 @@
  public:
   using MediaServiceGetter =
       base::RepeatingCallback<media::mojom::MediaService&()>;
-  using CreateInterfaceProviderCB = base::RepeatingCallback<
-      mojo::PendingRemote<service_manager::mojom::InterfaceProvider>()>;
+  using FrameServicesGetter = base::RepeatingCallback<
+      mojo::PendingRemote<media::mojom::FrameInterfaceFactory>()>;
 
   // |media_service_getter| will be called from the UI thread.
-  MediaInterfaceFactoryHolder(
-      MediaServiceGetter media_service_getter,
-      CreateInterfaceProviderCB create_interface_provider_cb);
+  MediaInterfaceFactoryHolder(MediaServiceGetter media_service_getter,
+                              FrameServicesGetter frame_services_getter);
   ~MediaInterfaceFactoryHolder();
 
   // Gets the MediaService |interface_factory_remote_|. The returned pointer is
@@ -44,7 +43,7 @@
   void OnMediaServiceConnectionError();
 
   MediaServiceGetter media_service_getter_;
-  CreateInterfaceProviderCB create_interface_provider_cb_;
+  FrameServicesGetter frame_services_getter_;
   mojo::Remote<media::mojom::InterfaceFactory> interface_factory_remote_;
 
   THREAD_CHECKER(thread_checker_);
diff --git a/content/browser/media/media_interface_proxy.cc b/content/browser/media/media_interface_proxy.cc
index 6aa962e..5352254b 100644
--- a/content/browser/media/media_interface_proxy.cc
+++ b/content/browser/media/media_interface_proxy.cc
@@ -22,8 +22,8 @@
 #include "content/public/browser/service_process_host.h"
 #include "content/public/common/content_client.h"
 #include "media/mojo/buildflags.h"
+#include "media/mojo/mojom/frame_interface_factory.mojom.h"
 #include "media/mojo/mojom/media_service.mojom.h"
-#include "media/mojo/services/media_interface_provider.h"
 
 #if BUILDFLAG(ENABLE_MOJO_CDM)
 #include "content/public/browser/browser_context.h"
@@ -185,6 +185,72 @@
   return *remote->get();
 }
 
+class FrameInterfaceFactoryImpl : public media::mojom::FrameInterfaceFactory {
+ public:
+#if BUILDFLAG(ENABLE_CDM_PROXY)
+  using CdmProxyCreator = base::RepeatingCallback<
+      void(const base::Token&, mojo::PendingReceiver<media::mojom::CdmProxy>)>;
+#endif
+
+  FrameInterfaceFactoryImpl(RenderFrameHost* rfh,
+#if BUILDFLAG(ENABLE_CDM_PROXY)
+                            CdmProxyCreator cdm_proxy_creator,
+#endif
+                            const base::Token& cdm_guid,
+                            const std::string& cdm_file_system_id)
+      : render_frame_host_(rfh),
+#if BUILDFLAG(ENABLE_CDM_PROXY)
+        cdm_proxy_creator_(std::move(cdm_proxy_creator)),
+#endif
+        cdm_guid_(cdm_guid),
+        cdm_file_system_id_(cdm_file_system_id) {
+  }
+
+  void CreateProvisionFetcher(
+      mojo::PendingReceiver<media::mojom::ProvisionFetcher> receiver) override {
+#if BUILDFLAG(ENABLE_MOJO_CDM)
+    ProvisionFetcherImpl::Create(
+        BrowserContext::GetDefaultStoragePartition(
+            render_frame_host_->GetProcess()->GetBrowserContext())
+            ->GetURLLoaderFactoryForBrowserProcess(),
+        std::move(receiver));
+#endif
+  }
+
+  void CreateCdmStorage(
+      mojo::PendingReceiver<media::mojom::CdmStorage> receiver) override {
+#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
+    // Only provide CdmStorageImpl when we have a valid |cdm_file_system_id|,
+    // which is currently only set for the CdmService (not the MediaService).
+    if (cdm_file_system_id_.empty())
+      return;
+
+    CdmStorageImpl::Create(render_frame_host_, cdm_file_system_id_,
+                           std::move(receiver));
+#endif
+  }
+
+#if BUILDFLAG(ENABLE_CDM_PROXY)
+  void CreateCdmProxy(
+      mojo::PendingReceiver<media::mojom::CdmProxy> receiver) override {
+    cdm_proxy_creator_.Run(cdm_guid_, std::move(receiver));
+  }
+#endif
+
+  void BindEmbedderReceiver(mojo::GenericPendingReceiver receiver) override {
+    GetContentClient()->browser()->BindMediaServiceReceiver(
+        render_frame_host_, std::move(receiver));
+  }
+
+ private:
+  RenderFrameHost* const render_frame_host_;
+#if BUILDFLAG(ENABLE_CDM_PROXY)
+  CdmProxyCreator cdm_proxy_creator_;
+#endif
+  const base::Token cdm_guid_;
+  const std::string cdm_file_system_id_;
+};
+
 }  // namespace
 
 MediaInterfaceProxy::MediaInterfaceProxy(
@@ -197,14 +263,13 @@
   DCHECK(render_frame_host_);
   DCHECK(!error_handler.is_null());
 
-  auto create_interface_provider_cb =
+  auto frame_factory_getter =
       base::BindRepeating(&MediaInterfaceProxy::GetFrameServices,
                           base::Unretained(this), base::Token(), std::string());
   media_interface_factory_ptr_ = std::make_unique<MediaInterfaceFactoryHolder>(
-      base::BindRepeating(&GetMediaService), create_interface_provider_cb);
+      base::BindRepeating(&GetMediaService), frame_factory_getter);
   secondary_interface_factory_ = std::make_unique<MediaInterfaceFactoryHolder>(
-      base::BindRepeating(&GetSecondaryMediaService),
-      create_interface_provider_cb);
+      base::BindRepeating(&GetSecondaryMediaService), frame_factory_getter);
 
   receiver_.set_disconnect_handler(std::move(error_handler));
 
@@ -335,48 +400,19 @@
 }
 #endif  // BUILDFLAG(ENABLE_CDM_PROXY)
 
-mojo::PendingRemote<service_manager::mojom::InterfaceProvider>
+mojo::PendingRemote<media::mojom::FrameInterfaceFactory>
 MediaInterfaceProxy::GetFrameServices(const base::Token& cdm_guid,
                                       const std::string& cdm_file_system_id) {
-  // Register frame services.
-  mojo::PendingRemote<service_manager::mojom::InterfaceProvider> interfaces;
-
-  // TODO(xhwang): Replace this InterfaceProvider with a dedicated media host
-  // interface. See http://crbug.com/660573
-  auto provider = std::make_unique<media::MediaInterfaceProvider>(
-      interfaces.InitWithNewPipeAndPassReceiver());
-
-#if BUILDFLAG(ENABLE_MOJO_CDM)
-  // TODO(slan): Wrap these into a RenderFrame specific ProvisionFetcher impl.
-  provider->registry()->AddInterface(base::BindRepeating(
-      &ProvisionFetcherImpl::Create,
-      base::RetainedRef(
-          BrowserContext::GetDefaultStoragePartition(
-              render_frame_host_->GetProcess()->GetBrowserContext())
-              ->GetURLLoaderFactoryForBrowserProcess())));
-
-#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
-  // Only provide CdmStorageImpl when we have a valid |cdm_file_system_id|,
-  // which is currently only set for the CdmService (not the MediaService).
-  if (!cdm_file_system_id.empty()) {
-    provider->registry()->AddInterface(base::BindRepeating(
-        &CdmStorageImpl::Create, render_frame_host_, cdm_file_system_id));
-  }
-
+  mojo::PendingRemote<media::mojom::FrameInterfaceFactory> factory;
+  frame_factories_.Add(
+      std::make_unique<FrameInterfaceFactoryImpl>(
+          render_frame_host_,
 #if BUILDFLAG(ENABLE_CDM_PROXY)
-  provider->registry()->AddInterface(
-      base::BindRepeating(&MediaInterfaceProxy::CreateCdmProxyInternal,
-                          base::Unretained(this), cdm_guid));
-#endif  // BUILDFLAG(ENABLE_CDM_PROXY)
-#endif  // BUILDFLAG(ENABLE_LIBRARY_CDMS)
-#endif  // BUILDFLAG(ENABLE_MOJO_CDM)
-
-  GetContentClient()->browser()->ExposeInterfacesToMediaService(
-      provider->registry(), render_frame_host_);
-
-  media_registries_.push_back(std::move(provider));
-
-  return interfaces;
+          base::BindRepeating(&CreateCdmProxyInternal, base::Unretained(this)),
+#endif
+          cdm_guid, cdm_file_system_id),
+      factory.InitWithNewPipeAndPassReceiver());
+  return factory;
 }
 
 #if BUILDFLAG(ENABLE_LIBRARY_CDMS)
diff --git a/content/browser/media/media_interface_proxy.h b/content/browser/media/media_interface_proxy.h
index 2f43fbe4..378d90c 100644
--- a/content/browser/media/media_interface_proxy.h
+++ b/content/browser/media/media_interface_proxy.h
@@ -24,12 +24,9 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/unique_receiver_set.h"
 #include "services/service_manager/public/mojom/interface_provider.mojom.h"
 
-namespace media {
-class MediaInterfaceProvider;
-}
-
 namespace content {
 
 class RenderFrameHost;
@@ -94,9 +91,9 @@
   // Gets services provided by the browser (at RenderFrameHost level) to the
   // mojo media (or CDM) service running remotely. |cdm_file_system_id| is
   // used to register the appropriate CdmStorage interface needed by the CDM.
-  mojo::PendingRemote<service_manager::mojom::InterfaceProvider>
-  GetFrameServices(const base::Token& cdm_guid,
-                   const std::string& cdm_file_system_id);
+  mojo::PendingRemote<media::mojom::FrameInterfaceFactory> GetFrameServices(
+      const base::Token& cdm_guid,
+      const std::string& cdm_file_system_id);
 
 #if BUILDFLAG(ENABLE_LIBRARY_CDMS)
   // Gets a CdmFactory pointer for |key_system|. Returns null if unexpected
@@ -133,9 +130,7 @@
   // Receiver for incoming InterfaceFactoryRequest from the the RenderFrameImpl.
   mojo::Receiver<InterfaceFactory> receiver_;
 
-  // TODO(xhwang): Replace InterfaceProvider with a dedicated host interface.
-  // See http://crbug.com/660573
-  std::vector<std::unique_ptr<media::MediaInterfaceProvider>> media_registries_;
+  mojo::UniqueReceiverSet<media::mojom::FrameInterfaceFactory> frame_factories_;
 
   // InterfacePtr to the remote InterfaceFactory implementation in the Media
   // Service hosted in the process specified by the "mojo_media_host" gn
diff --git a/content/browser/media/video_decoder_proxy.cc b/content/browser/media/video_decoder_proxy.cc
index c5ab6158..561c91e 100644
--- a/content/browser/media/video_decoder_proxy.cc
+++ b/content/browser/media/video_decoder_proxy.cc
@@ -94,7 +94,7 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(!interface_factory_remote_);
 
-  mojo::PendingRemote<service_manager::mojom::InterfaceProvider> interfaces;
+  mojo::PendingRemote<media::mojom::FrameInterfaceFactory> interfaces;
   ignore_result(interfaces.InitWithNewPipeAndPassReceiver());
 
   GetMediaService().CreateInterfaceFactory(
diff --git a/content/browser/sms/sms_fetcher_impl.cc b/content/browser/sms/sms_fetcher_impl.cc
index 96e75cd..0f4ef7ae 100644
--- a/content/browser/sms/sms_fetcher_impl.cc
+++ b/content/browser/sms/sms_fetcher_impl.cc
@@ -38,12 +38,13 @@
       context->GetUserData(kSmsFetcherImplKeyName));
 }
 
-SmsFetcher* SmsFetcher::Get(BrowserContext* context, RenderFrameHost* rfh) {
+SmsFetcher* SmsFetcher::Get(BrowserContext* context,
+                            base::WeakPtr<RenderFrameHost> rfh) {
   auto* stored_fetcher = static_cast<SmsFetcherImpl*>(
       context->GetUserData(kSmsFetcherImplKeyName));
   if (!stored_fetcher || !stored_fetcher->CanReceiveSms()) {
-    auto fetcher =
-        std::make_unique<SmsFetcherImpl>(context, SmsProvider::Create(rfh));
+    auto fetcher = std::make_unique<SmsFetcherImpl>(
+        context, SmsProvider::Create(std::move(rfh)));
     context->SetUserData(kSmsFetcherImplKeyName, std::move(fetcher));
   }
   return static_cast<SmsFetcherImpl*>(
diff --git a/content/browser/sms/sms_provider.cc b/content/browser/sms/sms_provider.cc
index 3372f762..734a64b2 100644
--- a/content/browser/sms/sms_provider.cc
+++ b/content/browser/sms/sms_provider.cc
@@ -24,14 +24,15 @@
 SmsProvider::~SmsProvider() = default;
 
 // static
-std::unique_ptr<SmsProvider> SmsProvider::Create(RenderFrameHost* rfh) {
+std::unique_ptr<SmsProvider> SmsProvider::Create(
+    base::WeakPtr<RenderFrameHost> rfh) {
 #if defined(OS_ANDROID)
   if (base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
           switches::kWebOtpBackend) ==
       switches::kWebOtpBackendSmsVerification) {
     return std::make_unique<SmsProviderGmsVerification>();
   }
-  return std::make_unique<SmsProviderGmsUserConsent>(rfh);
+  return std::make_unique<SmsProviderGmsUserConsent>(std::move(rfh));
 #else
   return nullptr;
 #endif
diff --git a/content/browser/sms/sms_provider.h b/content/browser/sms/sms_provider.h
index 1f19c7bb..63c69a8 100644
--- a/content/browser/sms/sms_provider.h
+++ b/content/browser/sms/sms_provider.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
 #include "content/browser/sms/sms_parser.h"
@@ -40,7 +41,8 @@
   // it is received or (exclusively) when it timeouts.
   virtual void Retrieve() = 0;
 
-  static std::unique_ptr<SmsProvider> Create(RenderFrameHost* rfh);
+  static std::unique_ptr<SmsProvider> Create(
+      base::WeakPtr<RenderFrameHost> rfh);
 
   void AddObserver(Observer*);
   void RemoveObserver(const Observer*);
diff --git a/content/browser/sms/sms_provider_gms_user_consent.cc b/content/browser/sms/sms_provider_gms_user_consent.cc
index 7ca74fd7..3164917 100644
--- a/content/browser/sms/sms_provider_gms_user_consent.cc
+++ b/content/browser/sms/sms_provider_gms_user_consent.cc
@@ -19,8 +19,9 @@
 
 namespace content {
 
-SmsProviderGmsUserConsent::SmsProviderGmsUserConsent(RenderFrameHost* rfh)
-    : SmsProvider(), render_frame_host_(rfh) {
+SmsProviderGmsUserConsent::SmsProviderGmsUserConsent(
+    base::WeakPtr<RenderFrameHost> rfh)
+    : SmsProvider(), render_frame_host_(std::move(rfh)) {
   // This class is constructed a single time whenever the
   // first web page uses the SMS Retriever API to wait for
   // SMSes.
@@ -35,13 +36,15 @@
 }
 
 void SmsProviderGmsUserConsent::Retrieve() {
-  JNIEnv* env = AttachCurrentThread();
+  if (!render_frame_host_)
+    return;
 
   WebContents* web_contents =
-      WebContents::FromRenderFrameHost(render_frame_host_);
+      WebContents::FromRenderFrameHost(render_frame_host_.get());
   if (!web_contents || !web_contents->GetTopLevelNativeWindow())
     return;
 
+  JNIEnv* env = AttachCurrentThread();
   Java_SmsUserConsentReceiver_listen(
       env, j_sms_receiver_,
       web_contents->GetTopLevelNativeWindow()->GetJavaObject());
diff --git a/content/browser/sms/sms_provider_gms_user_consent.h b/content/browser/sms/sms_provider_gms_user_consent.h
index 177a1fe..6aea26b 100644
--- a/content/browser/sms/sms_provider_gms_user_consent.h
+++ b/content/browser/sms/sms_provider_gms_user_consent.h
@@ -17,7 +17,7 @@
 
 class CONTENT_EXPORT SmsProviderGmsUserConsent : public SmsProvider {
  public:
-  SmsProviderGmsUserConsent(RenderFrameHost* rfh);
+  SmsProviderGmsUserConsent(base::WeakPtr<RenderFrameHost> rfh);
   ~SmsProviderGmsUserConsent() override;
 
   void Retrieve() override;
@@ -30,7 +30,7 @@
 
  private:
   base::android::ScopedJavaGlobalRef<jobject> j_sms_receiver_;
-  RenderFrameHost* const render_frame_host_;
+  const base::WeakPtr<RenderFrameHost> render_frame_host_;
 
   DISALLOW_COPY_AND_ASSIGN(SmsProviderGmsUserConsent);
 };
diff --git a/content/browser/sms/sms_provider_gms_user_consent_unittest.cc b/content/browser/sms/sms_provider_gms_user_consent_unittest.cc
index 425e263..7f65903 100644
--- a/content/browser/sms/sms_provider_gms_user_consent_unittest.cc
+++ b/content/browser/sms/sms_provider_gms_user_consent_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/test/scoped_feature_list.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
 #include "content/browser/sms/sms_provider.h"
 #include "content/browser/sms/sms_provider_gms_user_consent.h"
 #include "content/public/common/content_features.h"
@@ -49,7 +50,7 @@
   void SetUp() {
     RenderViewHostTestHarness::SetUp();
     provider_ = std::make_unique<SmsProviderGmsUserConsent>(
-        web_contents()->GetMainFrame());
+        static_cast<RenderFrameHostImpl*>(main_rfh())->GetWeakPtr());
     j_fake_sms_retriever_client_.Reset(
         Java_FakeSmsUserConsentRetrieverClient_create(AttachCurrentThread()));
     Java_SmsUserConsentFakes_setUserConsentClientForTesting(
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 19fe65c1..be4bb047 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -540,45 +540,53 @@
   switch (reason) {
     case net::CanonicalCookie::CookieInclusionStatus::
         WARN_SAMESITE_LAX_METHOD_UNSAFE_CROSS_SCHEME_SECURE_URL:
-      same_site_context.context = net::CookieOptions::SameSiteCookieContext::
-          ContextType::SAME_SITE_LAX_METHOD_UNSAFE;
-      same_site_context.cross_schemeness = net::CookieOptions::
-          SameSiteCookieContext::CrossSchemeness::INSECURE_SECURE;
+      same_site_context.set_context(
+          net::CookieOptions::SameSiteCookieContext::ContextType::
+              SAME_SITE_LAX_METHOD_UNSAFE);
+      same_site_context.set_cross_schemeness(
+          net::CookieOptions::SameSiteCookieContext::CrossSchemeness::
+              INSECURE_SECURE);
       return same_site_context.ConvertToMetricsValue();
     case net::CanonicalCookie::CookieInclusionStatus::
         WARN_SAMESITE_LAX_CROSS_SCHEME_SECURE_URL:
-      same_site_context.context =
-          net::CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX;
-      same_site_context.cross_schemeness = net::CookieOptions::
-          SameSiteCookieContext::CrossSchemeness::INSECURE_SECURE;
+      same_site_context.set_context(net::CookieOptions::SameSiteCookieContext::
+                                        ContextType::SAME_SITE_LAX);
+      same_site_context.set_cross_schemeness(
+          net::CookieOptions::SameSiteCookieContext::CrossSchemeness::
+              INSECURE_SECURE);
       return same_site_context.ConvertToMetricsValue();
     case net::CanonicalCookie::CookieInclusionStatus::
         WARN_SAMESITE_STRICT_CROSS_SCHEME_SECURE_URL:
-      same_site_context.context = net::CookieOptions::SameSiteCookieContext::
-          ContextType::SAME_SITE_STRICT;
-      same_site_context.cross_schemeness = net::CookieOptions::
-          SameSiteCookieContext::CrossSchemeness::INSECURE_SECURE;
+      same_site_context.set_context(net::CookieOptions::SameSiteCookieContext::
+                                        ContextType::SAME_SITE_STRICT);
+      same_site_context.set_cross_schemeness(
+          net::CookieOptions::SameSiteCookieContext::CrossSchemeness::
+              INSECURE_SECURE);
       return same_site_context.ConvertToMetricsValue();
     case net::CanonicalCookie::CookieInclusionStatus::
         WARN_SAMESITE_LAX_METHOD_UNSAFE_CROSS_SCHEME_INSECURE_URL:
-      same_site_context.context = net::CookieOptions::SameSiteCookieContext::
-          ContextType::SAME_SITE_LAX_METHOD_UNSAFE;
-      same_site_context.cross_schemeness = net::CookieOptions::
-          SameSiteCookieContext::CrossSchemeness::SECURE_INSECURE;
+      same_site_context.set_context(
+          net::CookieOptions::SameSiteCookieContext::ContextType::
+              SAME_SITE_LAX_METHOD_UNSAFE);
+      same_site_context.set_cross_schemeness(
+          net::CookieOptions::SameSiteCookieContext::CrossSchemeness::
+              SECURE_INSECURE);
       return same_site_context.ConvertToMetricsValue();
     case net::CanonicalCookie::CookieInclusionStatus::
         WARN_SAMESITE_LAX_CROSS_SCHEME_INSECURE_URL:
-      same_site_context.context =
-          net::CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX;
-      same_site_context.cross_schemeness = net::CookieOptions::
-          SameSiteCookieContext::CrossSchemeness::SECURE_INSECURE;
+      same_site_context.set_context(net::CookieOptions::SameSiteCookieContext::
+                                        ContextType::SAME_SITE_LAX);
+      same_site_context.set_cross_schemeness(
+          net::CookieOptions::SameSiteCookieContext::CrossSchemeness::
+              SECURE_INSECURE);
       return same_site_context.ConvertToMetricsValue();
     case net::CanonicalCookie::CookieInclusionStatus::
         WARN_SAMESITE_STRICT_CROSS_SCHEME_INSECURE_URL:
-      same_site_context.context = net::CookieOptions::SameSiteCookieContext::
-          ContextType::SAME_SITE_STRICT;
-      same_site_context.cross_schemeness = net::CookieOptions::
-          SameSiteCookieContext::CrossSchemeness::SECURE_INSECURE;
+      same_site_context.set_context(net::CookieOptions::SameSiteCookieContext::
+                                        ContextType::SAME_SITE_STRICT);
+      same_site_context.set_cross_schemeness(
+          net::CookieOptions::SameSiteCookieContext::CrossSchemeness::
+              SECURE_INSECURE);
       return same_site_context.ConvertToMetricsValue();
     default:
       // Return invalid value if there is no cross-scheme warning.
diff --git a/content/browser/worker_host/shared_worker_host.cc b/content/browser/worker_host/shared_worker_host.cc
index 59f103d..ac50a93 100644
--- a/content/browser/worker_host/shared_worker_host.cc
+++ b/content/browser/worker_host/shared_worker_host.cc
@@ -264,13 +264,13 @@
       default_factory_receiver =
           pending_default_factory.InitWithNewPipeAndPassReceiver();
 
-  const url::Origin& origin = instance_.constructor_origin();
+  url::Origin origin = url::Origin::Create(instance_.url());
 
   // TODO(https://crbug.com/1060832): Implement COEP reporter for shared
   // workers.
   network::mojom::URLLoaderFactoryParamsPtr factory_params =
       URLLoaderFactoryParamsHelper::CreateForWorker(
-          worker_process_host_, origin,
+          worker_process_host_, instance_.constructor_origin(),
           net::IsolationInfo::Create(
               net::IsolationInfo::RedirectMode::kUpdateNothing, origin, origin,
               net::SiteForCookies::FromOrigin(origin)),
diff --git a/content/browser/worker_host/worker_browsertest.cc b/content/browser/worker_host/worker_browsertest.cc
index eb9c123..c4d669f4 100644
--- a/content/browser/worker_host/worker_browsertest.cc
+++ b/content/browser/worker_host/worker_browsertest.cc
@@ -174,11 +174,24 @@
   }
 
   // Returns the cookie received with the request for the specified path. If the
-  // path was requested but no cookie was received, return kNoCookie.
+  // path was requested but no cookie was received, return kNoCookie. Waits for
+  // the path to be requested if it hasn't been requested already.
   std::string GetReceivedCookie(const std::string& path) {
+    {
+      base::AutoLock auto_lock(path_cookie_map_lock_);
+      DCHECK(path_to_wait_for_.empty());
+      DCHECK(!path_wait_loop_);
+      if (path_cookie_map_.find(path) != path_cookie_map_.end())
+        return path_cookie_map_[path];
+      path_to_wait_for_ = path;
+      path_wait_loop_ = std::make_unique<base::RunLoop>();
+    }
+
+    path_wait_loop_->Run();
+
     base::AutoLock auto_lock(path_cookie_map_lock_);
-    if (path_cookie_map_.find(path) == path_cookie_map_.end())
-      return "path not requested";
+    path_to_wait_for_.clear();
+    path_wait_loop_.reset();
     return path_cookie_map_[path];
   }
 
@@ -213,9 +226,12 @@
     auto cookie_header = request.headers.find("Cookie");
     if (cookie_header == request.headers.end()) {
       path_cookie_map_[request.relative_url] = kNoCookie;
-      return nullptr;
+    } else {
+      path_cookie_map_[request.relative_url] = cookie_header->second;
     }
-    path_cookie_map_[request.relative_url] = cookie_header->second;
+    if (path_to_wait_for_ == request.relative_url) {
+      path_wait_loop_->Quit();
+    }
     return nullptr;
   }
 
@@ -223,6 +239,14 @@
   // with. Paths may only be requested once without clearing the map.
   std::map<std::string, std::string> path_cookie_map_
       GUARDED_BY(path_cookie_map_lock_);
+  // If non-empty, path to wait for the test server to see a request for on the
+  // "a.test" server.
+  std::string path_to_wait_for_ GUARDED_BY(path_cookie_map_lock_);
+  // If non-null, quit when a request for |path_to_wait_for_| is observed. May
+  // only be created or dereferenced off of the UI thread while holding
+  // |path_cookie_map_lock_|, its run method must be called while not holding
+  // the lock.
+  std::unique_ptr<base::RunLoop> path_wait_loop_;
   // Lock that must be held while modifying |path_cookie_map_|, as it's used on
   // both the test server's thread and the UI thread.
   base::Lock path_cookie_map_lock_;
@@ -396,17 +420,18 @@
 }
 
 // Tests the value of |request_initiator| for shared worker resources.
-IN_PROC_BROWSER_TEST_P(WorkerTest, VerifyInitiatorSharedWorker) {
+IN_PROC_BROWSER_TEST_P(WorkerTest,
+                       VerifyInitiatorAndSameSiteCookiesSharedWorker) {
   if (!SupportsSharedWorker())
     return;
 
-  const GURL start_url(ssl_server()->GetURL("a.test", "/frame_tree/top.html"));
+  const GURL start_url(ssl_server()->GetURL("b.test", "/frame_tree/top.html"));
   EXPECT_TRUE(NavigateToURL(shell(), start_url));
 
   // To make things tricky about |top_frame_origin|, this test navigates to
   // a page on |ssl_server()| which has a cross-origin iframe that registers the
   // worker.
-  std::string cross_site_domain("b.test");
+  std::string cross_site_domain("a.test");
   const GURL test_url(ssl_server()->GetURL(
       cross_site_domain, "/workers/simple_shared_worker.html"));
 
@@ -421,6 +446,9 @@
   const GURL resource_url(
       ssl_server()->GetURL(cross_site_domain, "/workers/empty.html"));
 
+  // Set a cookie for verfifying which requests send SameSite cookies.
+  SetSameSiteCookie(cross_site_domain);
+
   std::set<GURL> expected_request_urls = {worker_url, script_url, resource_url};
   const url::Origin expected_origin =
       url::Origin::Create(worker_url.GetOrigin());
@@ -445,6 +473,14 @@
                             ->root();
   NavigateFrameToURL(root->child_at(0), test_url);
   waiter.Run();
+
+  // Check cookies sent with each request to "a.test". Frame request should not
+  // have SameSite cookies, but SharedWorker could (though eventually this will
+  // need to be changed, to protect against cross-site user tracking).
+  EXPECT_EQ(kNoCookie, GetReceivedCookie(test_url.path()));
+  EXPECT_EQ(kSameSiteCookie, GetReceivedCookie(worker_url.path()));
+  EXPECT_EQ(kSameSiteCookie, GetReceivedCookie(script_url.path()));
+  EXPECT_EQ(kSameSiteCookie, GetReceivedCookie(resource_url.path()));
 }
 
 // Test that an "a.test" worker sends "a.test" SameSite cookies, both when
diff --git a/content/browser/xr/service/browser_xr_runtime_impl.cc b/content/browser/xr/service/browser_xr_runtime_impl.cc
index 72517b9..81479637 100644
--- a/content/browser/xr/service/browser_xr_runtime_impl.cc
+++ b/content/browser/xr/service/browser_xr_runtime_impl.cc
@@ -161,6 +161,7 @@
     device::mojom::XRSessionFeature::REF_SPACE_LOCAL,
     device::mojom::XRSessionFeature::REF_SPACE_LOCAL_FLOOR,
     device::mojom::XRSessionFeature::REF_SPACE_UNBOUNDED,
+    device::mojom::XRSessionFeature::DOM_OVERLAY,
 };
 
 #if BUILDFLAG(ENABLE_OPENVR)
@@ -257,11 +258,6 @@
     case device::mojom::XRDeviceId::FAKE_DEVICE_ID:
       return true;
     case device::mojom::XRDeviceId::ARCORE_DEVICE_ID:
-      // Only support DOM overlay if the feature flag is enabled.
-      if (feature == device::mojom::XRSessionFeature::DOM_OVERLAY) {
-        return base::FeatureList::IsEnabled(features::kWebXrIncubations);
-      }
-
       // Only support hit test if the feature flag is enabled.
       if (feature == device::mojom::XRSessionFeature::HIT_TEST) {
         return base::FeatureList::IsEnabled(features::kWebXrHitTest);
diff --git a/content/common/background_fetch/background_fetch_types.cc b/content/common/background_fetch/background_fetch_types.cc
index 9222c878..cdf910a4 100644
--- a/content/common/background_fetch/background_fetch_types.cc
+++ b/content/common/background_fetch/background_fetch_types.cc
@@ -38,6 +38,7 @@
       CloneSerializedBlob(response->side_data_blob),
       CloneSerializedBlob(response->side_data_blob_for_cache_put),
       mojo::Clone(response->content_security_policy),
+      response->cross_origin_embedder_policy,
       response->loaded_with_credentials);
 }
 
diff --git a/content/common/service_worker/service_worker_loader_helpers.cc b/content/common/service_worker/service_worker_loader_helpers.cc
index 35b3053a..d11c341 100644
--- a/content/common/service_worker/service_worker_loader_helpers.cc
+++ b/content/common/service_worker/service_worker_loader_helpers.cc
@@ -14,7 +14,6 @@
 #include "content/public/common/content_features.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "net/http/http_util.h"
-#include "net/http/structured_headers.h"
 #include "net/url_request/redirect_util.h"
 #include "services/network/public/cpp/content_security_policy/content_security_policy.h"
 #include "services/network/public/cpp/cross_origin_embedder_policy.h"
@@ -49,37 +48,6 @@
   BlobCompleteCallback callback_;
 };
 
-std::pair<network::mojom::CrossOriginEmbedderPolicyValue,
-          base::Optional<std::string>>
-ParseCrossOriginEmbedderPolicyValueInternal(
-    const net::HttpResponseHeaders* headers,
-    base::StringPiece header_name) {
-  static constexpr char kRequireCorp[] = "require-corp";
-  constexpr auto kNone = network::mojom::CrossOriginEmbedderPolicyValue::kNone;
-  using Item = net::structured_headers::Item;
-  std::string header_value;
-  if (!headers ||
-      !headers->GetNormalizedHeader(header_name.as_string(), &header_value)) {
-    return std::make_pair(kNone, base::nullopt);
-  }
-  const auto item = net::structured_headers::ParseItem(header_value);
-  if (!item || item->item.Type() != Item::kTokenType ||
-      item->item.GetString() != kRequireCorp) {
-    return std::make_pair(kNone, base::nullopt);
-  }
-  base::Optional<std::string> endpoint;
-  auto it = std::find_if(item->params.cbegin(), item->params.cend(),
-                         [](const std::pair<std::string, Item>& param) {
-                           return param.first == "report-to";
-                         });
-  if (it != item->params.end() && it->second.Type() == Item::kStringType) {
-    endpoint = it->second.GetString();
-  }
-  return std::make_pair(
-      network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp,
-      std::move(endpoint));
-}
-
 }  // namespace
 
 // static
@@ -124,28 +92,6 @@
   if (out_head->content_length == -1)
     out_head->content_length = out_head->headers->GetContentLength();
 
-  // TODO(yhirano): Remove the code duplication with
-  // //services/network/url_loader.cc.
-  if (base::FeatureList::IsEnabled(
-          network::features::kCrossOriginEmbedderPolicy)) {
-    // Parse the Cross-Origin-Embedder-Policy and
-    // Cross-Origin-Embedder-Policy-Report-Only headers.
-
-    static constexpr char kCrossOriginEmbedderPolicyValueHeader[] =
-        "Cross-Origin-Embedder-Policy";
-    static constexpr char kCrossOriginEmbedderPolicyValueReportOnlyHeader[] =
-        "Cross-Origin-Embedder-Policy-Report-Only";
-    network::CrossOriginEmbedderPolicy coep;
-    std::tie(coep.value, coep.reporting_endpoint) =
-        ParseCrossOriginEmbedderPolicyValueInternal(
-            out_head->headers.get(), kCrossOriginEmbedderPolicyValueHeader);
-    std::tie(coep.report_only_value, coep.report_only_reporting_endpoint) =
-        ParseCrossOriginEmbedderPolicyValueInternal(
-            out_head->headers.get(),
-            kCrossOriginEmbedderPolicyValueReportOnlyHeader);
-    out_head->cross_origin_embedder_policy = coep;
-  }
-
   // TODO(pmeuleman): Remove the code duplication with
   // //services/network/url_loader.cc.
   if (base::FeatureList::IsEnabled(
@@ -183,6 +129,8 @@
   out_head->did_service_worker_navigation_preload = false;
   out_head->content_security_policy =
       mojo::Clone(response.content_security_policy);
+  out_head->cross_origin_embedder_policy =
+      response.cross_origin_embedder_policy;
 }
 
 // static
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 163872d8..dd059509 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -979,6 +979,11 @@
       blink::AssociatedInterfaceRegistry* associated_registry,
       RenderProcessHost* render_process_host) {}
 
+  // Called to bind additional frame-bound media interfaces to the renderer.
+  virtual void BindMediaServiceReceiver(RenderFrameHost* render_frame_host,
+                                        mojo::GenericPendingReceiver receiver) {
+  }
+
   // Called when RenderFrameHostImpl connects to the Media service. Expose
   // interfaces to the service using |registry|.
   virtual void ExposeInterfacesToMediaService(
diff --git a/content/public/browser/sms_fetcher.h b/content/public/browser/sms_fetcher.h
index e0b5202..47b1da5 100644
--- a/content/public/browser/sms_fetcher.h
+++ b/content/public/browser/sms_fetcher.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/memory/weak_ptr.h"
 #include "base/observer_list_types.h"
 #include "content/common/content_export.h"
 
@@ -30,7 +31,7 @@
   // Retrieval for devices that have telephony capabilities and can receive
   // SMSes coming from the installed device locally. (eg. Android phones)
   CONTENT_EXPORT static SmsFetcher* Get(BrowserContext* context,
-                                        RenderFrameHost* rfh);
+                                        base::WeakPtr<RenderFrameHost> rfh);
 
   class Subscriber : public base::CheckedObserver {
    public:
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index aa3efe3..7a9e88d 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -273,6 +273,11 @@
 const base::Feature kInstalledAppProvider{"InstalledAppProvider",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Show warning about clearing data from installed apps in the clear browsing
+// data flow. The warning will be shown in a second dialog.
+const base::Feature kInstalledAppsInCbd{"InstalledAppsInCbd",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Alternative to switches::kIsolateOrigins, for turning on origin isolation.
 // List of origins to isolate has to be specified via
 // kIsolateOriginsFieldTrialParamName.
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 682441a..2009916 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -67,6 +67,7 @@
 CONTENT_EXPORT extern const base::Feature kIdleDetection;
 CONTENT_EXPORT extern const base::Feature kInputPredictorTypeChoice;
 CONTENT_EXPORT extern const base::Feature kInstalledAppProvider;
+CONTENT_EXPORT extern const base::Feature kInstalledAppsInCbd;
 CONTENT_EXPORT extern const base::Feature kIsolateOrigins;
 CONTENT_EXPORT extern const char kIsolateOriginsFieldTrialParamName[];
 CONTENT_EXPORT extern const base::Feature kLazyFrameLoading;
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index f2bc6569..8f514c11 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -4251,39 +4251,25 @@
                "id", routing_id_,
                "url", GetLoadingUrl().possibly_invalid_spec());
 
-  base::Optional<base::UnguessableToken> embedding_token = base::nullopt;
-  if (previous_routing_id_ != MSG_ROUTING_NONE) {
+  bool is_provisional_frame = previous_routing_id_ != MSG_ROUTING_NONE;
+  if (is_provisional_frame) {
     // If this is a provisional frame associated with a proxy (i.e., a frame
     // created for a remote-to-local navigation), swap it into the frame tree
     // now.
     if (!SwapIn())
       return;
+  }
 
-    // Main frames don't require an embedding token since they aren't embedded
-    // in anything. Frames local to their parent also aren't considered to be
-    // embedded.
-    if (!is_main_frame_ && frame_->Parent()->IsWebRemoteFrame()) {
-      embedding_token = base::UnguessableToken::Create();
-      GetWebFrame()->SetEmbeddingToken(embedding_token.value());
-    }
-  } else {
-    // If this is not a provisional frame then use the old embedding token. This
-    // will be base::nullopt if there was no old embedding token.
-    embedding_token = GetWebFrame()->GetEmbeddingToken();
-
-    // In the case a crashed subframe is navigating to the same site
-    // e.g. https://crbug.com/634368 then generate a new embedding token to
-    // restore a sane state.
-    //
-    // Logic behind this behavior:
-    // - Main frames don't have embedding tokens.
-    // - A remote subframe *must* have an embedding token.
-    // - If a remote subframe doesn't have an embedding token to re-use we
-    //   need to create one.
-    if (!is_main_frame_ && frame_->Parent()->IsWebRemoteFrame() &&
-        !embedding_token.has_value()) {
-      embedding_token = base::UnguessableToken::Create();
-      GetWebFrame()->SetEmbeddingToken(embedding_token.value());
+  // Main frames don't require an embedding token since they aren't embedded
+  // in anything. Frames local to their parent also aren't considered to be
+  // embedded.
+  if (!is_main_frame_ && frame_->Parent()->IsWebRemoteFrame()) {
+    // Provisional frames need a new token. Non-provisional frames need one if
+    // they don't already have one, e.g. a crashed subframe navigating to same
+    // site (https://crbug.com/634368).
+    if (is_provisional_frame ||
+        !GetWebFrame()->GetEmbeddingToken().has_value()) {
+      GetWebFrame()->SetEmbeddingToken(base::UnguessableToken::Create());
     }
   }
 
@@ -4377,7 +4363,7 @@
                 std::move(remote_interface_provider_receiver),
                 std::move(browser_interface_broker_receiver))
           : nullptr,
-      embedding_token);
+      GetWebFrame()->GetEmbeddingToken());
 
   // If we end up reusing this WebRequest (for example, due to a #ref click),
   // we don't want the transition type to persist.  Just clear it.
diff --git a/content/test/gpu/gpu_tests/gpu_helper.py b/content/test/gpu/gpu_tests/gpu_helper.py
index dd651ac..885aa7f6 100644
--- a/content/test/gpu/gpu_tests/gpu_helper.py
+++ b/content/test/gpu/gpu_tests/gpu_helper.py
@@ -130,10 +130,15 @@
 def GetSkiaRenderer(extra_browser_args):
   if extra_browser_args:
     for o in extra_browser_args:
-      if "UseSkiaRenderer" in o:
+      if o.startswith('--enable-features') and "UseSkiaRenderer" in o:
         return 'skia-renderer'
+      if o.startswith('--disable-features') and "UseSkiaRenderer" in o:
+        return 'no-skia-renderer'
       if "--disable-vulkan-fallback-to-gl-for-testing" in o:
         return 'skia-renderer'
+  # TODO(kylechar): The feature is enabled/disabled differently depending on
+  # platform and official build status. Find out if SkiaRenderer is enabled
+  # through GPU info instead.
   return 'no-skia-renderer'
 
 
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index 4846bc91..a357d19 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -166,6 +166,12 @@
 crbug.com/1008450 [ android use-vulkan skia-renderer ] Pixel_Video_BackdropFilter [ Skip ]
 crbug.com/1008450 [ android skia-renderer ] Pixel_Video_MP4_Rounded_Corner [ Skip ]
 
+# Test is producing a large number of visually identical images with minor (eg.
+# between one and four pixels off by a tiny bit) pixel diffs on Windows with
+# GLRenderer. There are too many images to add to gold so just disable test
+# until we can drop GLRenderer from FYI bots.
+crbug.com/1068301 [ win no-skia-renderer ] Pixel_DirectComposition_Video_MP4_Rounded_Corner [ Skip ]
+
 # Produces blank images on Intel HD 630 w/ Mesa 19.0.2
 crbug.com/976861 [ linux intel-0x5912 ] Pixel_OffscreenCanvasTransferToImageBitmap [ Skip ]
 
diff --git a/content/utility/services.cc b/content/utility/services.cc
index 977a53e..56c5fc7 100644
--- a/content/utility/services.cc
+++ b/content/utility/services.cc
@@ -62,7 +62,7 @@
 #if BUILDFLAG(ENABLE_LIBRARY_CDMS)
 
 std::unique_ptr<media::CdmAuxiliaryHelper> CreateCdmHelper(
-    service_manager::mojom::InterfaceProvider* interface_provider) {
+    media::mojom::FrameInterfaceFactory* interface_provider) {
   return std::make_unique<media::MojoCdmHelper>(interface_provider);
 }
 
@@ -80,9 +80,9 @@
   }
 
   std::unique_ptr<media::CdmFactory> CreateCdmFactory(
-      service_manager::mojom::InterfaceProvider* host_interfaces) override {
+      media::mojom::FrameInterfaceFactory* frame_interfaces) override {
     return std::make_unique<media::CdmAdapterFactory>(
-        base::BindRepeating(&CreateCdmHelper, host_interfaces));
+        base::BindRepeating(&CreateCdmHelper, frame_interfaces));
   }
 
 #if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION)
diff --git a/docs/accessibility/autoclick.md b/docs/accessibility/autoclick.md
index 298d5b3b..dd088bb 100644
--- a/docs/accessibility/autoclick.md
+++ b/docs/accessibility/autoclick.md
@@ -30,7 +30,7 @@
 [this template](https://bugs.chromium.org/p/chromium/issues/entry?summary=Autoclick%20-%20&status=Available&cc=katie%40chromium.org%2C%20qqwangxin%40google.com&labels=Pri-3%2C%20autoclick%2C&components=UI>Accessibility)).
 
 
-Open bugs have the label 
+Open bugs have the label
 “[autoclick](https://bugs.chromium.org/p/chromium/issues/list?can=2&q=label%3Aautoclick)”.
 
 ## Developing
@@ -138,7 +138,7 @@
 at the scroll location, then walks up the tree to find the first view which can
 scroll, or stops at the nearest window or dialog bounds. This logic takes place
 in autoclick.js, onAutomationHitTestResult_. When the scrolling location is
-found, the bounds of the scrollable area are highlighted with a focus ring. 
+found, the bounds of the scrollable area are highlighted with a focus ring.
 In addition, the bounds are sent back through the AccessibilityPrivate API,
 routed to the AutoclickController, which passes it via the
 AutoclickMenuBubbleController to the AutoclickScrollBubbleController, which
@@ -165,9 +165,9 @@
 The autoclick bubble menu can be positioned in the four corners of the screen
 and defaults to the same location as the volume widget (which depends on
 LTR/RTL language). AutoclickMenuBubbleController takes a preferred
-AutoclickMenuPosition enum and uses that to determine the best position for
+FloatingMenuPosition enum and uses that to determine the best position for
 the menu in AutoclickMenuBubbleController::SetPosition. This function finds
-the ideal corner of the screen, then uses CollisionDetectionUtils (also used 
+the ideal corner of the screen, then uses CollisionDetectionUtils (also used
 by Picture-in-Picture) to refine the position to avoid collisions with system
 UI.
 
@@ -178,7 +178,7 @@
 selected, if the scrollable region found by the Autoclick component extension
 is large enough, the scroll bubble will be anchored near the scroll point
 itself, similarly to the way the context menu is anchored near the cursor on
-a right click. When the scrollable region is small, the scroll bubble will be 
+a right click. When the scrollable region is small, the scroll bubble will be
 anchored to the closest side of the scrollable region to the scroll point, as
 long as there is space for it on that side.
 
@@ -188,10 +188,10 @@
 bubbles, because that would cause context and focus changes. For example, if
 the user has a drop-down menu open, clicking the autoclick menu bubble will
 cause the drop-down to close. Instead, the AutoclickController must check to
-see if an event will take place over a bubble menu, and if so, request that 
-AutoclickMenuBubbleController forward the event to the bubble via 
+see if an event will take place over a bubble menu, and if so, request that
+AutoclickMenuBubbleController forward the event to the bubble via
 AutoclickMenuBubbleController::ClickOnBubble. This generates a synthetic mouse
-event which does not propagate through the system, so there is no focus or 
+event which does not propagate through the system, so there is no focus or
 context change, allowing users to continue to interact with whatever was on
 screen.
 
diff --git a/extensions/browser/browser_context_keyed_service_factories.cc b/extensions/browser/browser_context_keyed_service_factories.cc
index 5fdadc8..0e5db0e 100644
--- a/extensions/browser/browser_context_keyed_service_factories.cc
+++ b/extensions/browser/browser_context_keyed_service_factories.cc
@@ -34,6 +34,7 @@
 #include "extensions/browser/api/usb/usb_device_resource.h"
 #include "extensions/browser/api/web_request/web_request_api.h"
 #include "extensions/browser/app_window/app_window_geometry_cache.h"
+#include "extensions/browser/app_window/app_window_registry.h"
 #include "extensions/browser/declarative_user_script_manager_factory.h"
 #include "extensions/browser/event_router_factory.h"
 #include "extensions/browser/extension_message_filter.h"
@@ -51,6 +52,10 @@
 #include "extensions/browser/api/webcam_private/webcam_private_api.h"
 #endif
 
+#if defined(OS_CHROMEOS)
+#include "extensions/browser/api/system_power_source/system_power_source_api.h"
+#endif
+
 namespace extensions {
 
 void EnsureBrowserContextKeyedServiceFactoriesBuilt() {
@@ -62,6 +67,7 @@
   ApiResourceManager<Socket>::GetFactoryInstance();
   ApiResourceManager<UsbDeviceResource>::GetFactoryInstance();
   AppWindowGeometryCache::Factory::GetInstance();
+  AppWindowRegistry::Factory::GetInstance();
   AudioAPI::GetFactoryInstance();
   BluetoothAPI::GetFactoryInstance();
   BluetoothPrivateAPI::GetFactoryInstance();
diff --git a/extensions/common/api/automation.idl b/extensions/common/api/automation.idl
index f380faf..a0028b1 100644
--- a/extensions/common/api/automation.idl
+++ b/extensions/common/api/automation.idl
@@ -1005,6 +1005,9 @@
     // Indicates the font family.
     DOMString fontFamily;
 
+    // Indicates whether this is a root of an editable subtree.
+    boolean editableRoot;
+
     //
     // Walking the tree.
     //
diff --git a/extensions/common/api/test.json b/extensions/common/api/test.json
index 5772972..1bd9315 100644
--- a/extensions/common/api/test.json
+++ b/extensions/common/api/test.json
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// See //extensions/docs/testing_api.md for how to use this API.
 [
   {
     "namespace": "test",
diff --git a/extensions/docs/extension_tests.md b/extensions/docs/extension_tests.md
index 59addd481..eb03fe4 100644
--- a/extensions/docs/extension_tests.md
+++ b/extensions/docs/extension_tests.md
@@ -194,8 +194,8 @@
 
 #### ResultCatcher
 A helper class to wait for the success or failure result of an extension test
-from an extension using the `chrome.test` API (TODO(devlin): Add a doc on how to
-use the chrome.test API).  This class is only useful in browser tests.
+from an extension using the `chrome.test` API. This class is only useful in
+browser tests.
 
 **Example Usage**
 
@@ -326,8 +326,5 @@
 ]);
 ```
 
-Though slightly outdated,
-[this README](/chrome/test/data/extensions/api_test/README.txt) also includes
-some advice on how to write extension API tests.
-(TODO(devlin): Add more documentation on how to use chrome.test in this doc,
-and remove the old readme.)
+See [Using the chrome.test API](/extensions/docs/testing_api.md) for more
+information on how to write extension API tests.
diff --git a/extensions/docs/testing_api.md b/extensions/docs/testing_api.md
new file mode 100644
index 0000000..929c563
--- /dev/null
+++ b/extensions/docs/testing_api.md
@@ -0,0 +1,447 @@
+# Using the chrome.test API
+
+[TOC]
+
+## What is It?
+The `chrome.test` extension API is a limited testing framework implemented as
+an extension API.  It is primarily used in order to provide testing
+functionality used in writing extension API tests by exercising an API directly
+in JS.  See also [writing extension tests].
+
+### Basic JS-Based Tests
+All tests must have some limited C++ portion (in order to kick off and drive
+the test).  In the most basic form, this C++ test only needs to load the
+extension and wait for the response from the testing framework.  This is easily
+accomplished through either `ExtensionApiTest::RunExtensionTest()` or
+`ExtensionApiTest::RunExtensionSubtest()` (and their variants).
+
+`ExtensionApiTest::RunExtensionTest()` loads a test extension and then waits
+for the result from the testing framework.
+`ExtensionApiTest::RunExtensionSubtest()` loads a test extension, navigates to
+a subpage of that extension, and then waits for the result from the testing
+framework.
+
+### Advanced Tests
+More advanced tests may require more synchronization between the C++ and the JS
+(e.g., to set up or verify state on the C++ side before continuing on the JS
+side).  To do this, leverage either `extensions::ResultCatcher`, which waits
+for the next result from the testing framework (sent using either
+`chrome.test.notifyPass()`, `chrome.test.notifyFail()`, or waiting for the
+result of `chrome.test.runTests()`, which calls `notifyPass()` or
+`notifyFail()` automatically), or `extensions::ExtensionTestMessageListener`,
+which waits for a message sent by `chrome.test.sendMessage()`.  See
+[writing extension tests] for more information.
+
+## Using the API
+The API provides a variety of different methods, used for different purposes.
+
+### notifyPass() and notifyFail()
+`chrome.test.notifyPass()` and `chrome.test.notifyFail()` are used in order to
+pass the result of running the JS test to the C++.  This is the result that
+`RunExtensionTest()`, `RunExtensionSubtest()`, and the `ResultCatcher` wait on.
+In order to explicitly pass this result, call `chrome.test.notifyPass()` or
+`chrome.test.notifyFail()`.
+
+```js
+chrome.tabs.create(() => {
+  <Verify state>
+  chrome.test.notifyPass();
+});
+```
+
+`notifyPass()` and `notifyFail()` may also be implicitly called by the testing
+API, as described in the sections below.
+
+### test.runTests()
+`chrome.test.runTests()` is used to run a sequence of individual, smaller JS
+tests, and then passes the result to the browser by **automatically** calling
+`chrome.test.notifyPass()` or `chrome.test.notifyFail()`.  `notifyPass()` will
+be called if and only if all individual tests pass; `notifyFail()` will be
+called if any test fails.  A test may fail if an assertion fails, if there is
+an unexpected runtime error, or if `chrome.test.fail()` is called explicitly.
+
+`chrome.test.runTests()` takes an array of functions, and runs them serially.
+This means that these functions may be independent, or may implicitly rely on
+one another.  The output of running these individual tests is printed through
+`console.log()`s, which enables tracing how far a test suite progresses.
+
+Each individual test function passed to `runTests()` will execute, and then
+wait for that specific function to pass or fail.  Passing is indicated by
+calling `chrome.test.succeed()` within each test function (**not**
+`chrome.test.notifyPass()`, which will automatically indicate the entire JS
+test passes, and may mask failures - see also the [Do's And Don't's]. Failure
+is indicated by calling `chrome.test.fail()`, a failed assertion, or through
+an unexpected runtime error or API error (indicated in
+`chrome.runtime.lastError`). Each test function must signal success or failure;
+otherwise the test will hang (and eventually timeout).
+
+A sample test suite may look like this.
+
+```js
+let tabId;
+
+chrome.test.runTests([
+  function createNewTab() {
+    chrome.tabs.create({url: 'http://example.com'}, (tab) => {
+      chrome.test.assertNoLastError();
+      <verify |tab| properties>
+      tabId = tab.id;
+      chrome.test.succeed();
+    });
+  },
+  function queryTab() {
+    chrome.tabs.query({url: 'http://example.com'}, (tabs) => {
+      chrome.test.assertNoLastError();
+      <verify |tabs|>
+      chrome.test.succeed();
+    });
+  },
+  function removeTab() {
+    chrome.tabs.remove(tabId, () => {
+      chrome.test.assertNoLastError();
+      chrome.test.succeed();
+    });
+  },
+]);
+```
+
+### Assertions
+The ``chrome.test API`` provides a number of basic assertion methods.
+
+#### assertTrue(condition, message?)
+Asserts that the given condition is true, printing out the optional error
+message if it is not.
+
+#### assertFalse(condition, message?)
+Asserts that the given condition is false, printing out the optional error
+message if it is not.
+
+#### assertEq(expected, actual, message?)
+Asserts that the provided value matches the expected value.  If `expected` is
+an object, this will perform a deep-equals check (i.e., verifying that two
+objects are logically equivalent, rather than have the same address).  If the
+expected value does not match the actual value, this will print out the
+expected and actual values (through `JSON.stringify()` for objects).
+
+#### assertNoLastError()
+Asserts that `chrome.runtime.lastError` is undefined, printing out the error
+otherwise.
+
+#### assertLastError(expectedError)
+Asserts that `chrome.runtime.lastError.message` is equivalent to
+`expectedError`, printing out the expected and actual errors otherwise.
+
+#### assertThrows(fn, self?, args, expectedError?)
+Asserts that executing `fn` with the context object of `self` (if defined) and
+the specified `arguments` throws a runtime error, which is then validated
+against `expectedError`.  `expectedError` may be either a string (which must
+match exactly) or a `RegExp`.
+
+### callbackPass() and callbackFail()
+**Important Notes:**
+- `callbackPass()` and `callbackFail()` should absolutely **only** be used
+  inside of `chrome.test.runTests()`.
+- `callbackPass()` and `callbackFail()` are no longer as useful as they were,
+  and sometimes result in less readable, more surprising code.  Think twice
+  (or even thrice!) before using them.
+
+`callbackPass()` and `callbackFail()` were primarily used when tests needed to
+wait on two non-deterministic functions.  For instance, consider the
+(contrived) following test:
+
+```js
+chrome.test.runTests([
+  function step1() {
+    chrome.tabs.create({url: 'http://example.com'}, () => {
+      <verify state>
+    });
+    chrome.storage.local.set({foo: 'bar'}, () => {
+      <verify state>
+    });
+    // Somehow end the test when both the tab creation and storage set have
+    // finished.
+  },
+  function step2() {
+    ...
+  }
+]);
+```
+
+Here, we want to have `step1()` finish after both the new tab has been created
+and a storage value has been set (again, this is admittedly contrived).  There
+is no hard guarantee about which function will finish first, so putting a
+chrome.test.succeed() call in either may result in succeeding and continuing to
+the next step too early.  Putting a `chrome.test.succeed()` call in both will
+result in badness (see the [Do's And Don't's]).
+
+`callbackPass()` lets the testing infrastructure handle this.  `callbackPass()`
+takes a function to invoke as an argument, and wraps it.  When `callbackPass()`
+is used (or any other function that increments an internal callback counter),
+the testing infrastructure keeps track of that callback and waits for all
+outstanding callbacks to be called before automatically continuing to the next
+test.  A version of this test using `callbackPass()` may look like this.
+
+```js
+chrome.test.runTests([
+  function step1() {
+    chrome.tabs.create({url: 'http://example.com'}, callbackPass(() => {
+      <verify state>
+    }));
+    chrome.storage.local.set({foo: 'bar'}, callbackPass(() => {
+      <verify state>
+    }));
+  },
+  function step2() {
+    ...
+  }
+]);
+```
+
+Now, `chrome.test.succeed()` will be internally called by the testing
+infrastructure once both callbacks have been invoked.
+
+`callbackFail()` behaves similarly to `callbackPass()`, except that it
+predominantly takes an expected error message that will be set in
+`chrome.runtime.lastError` (though it takes a secondary argument of an optional
+function to invoke).
+
+#### Advantages
+The most obvious advantage to using `callbackPass()` and `callbackFail()` is
+that is eliminates the need for more complex callback management.  In addition
+to keeping track of the callbacks, callbackPass() also automatically checks
+that there was no API error raised in the callback, obviating the need for a
+call to `chrome.test.assertNoLastError()`.  This can lead to more succinct
+code.
+
+#### Disadvantages
+`callbackPass()` and `callbackFail()` can both lead to much less readable and
+obvious code.  Extension APIs (and JavaScript in general) are already heavily
+asynchronous, and use of `callbackPass()` and `callbackFail()` can lead to even
+more confusion about when a test will finish, or which order items will be
+executed in.  Coupled with the internal callback counter, hazards around
+multiple calls to `chrome.test.succeed()` or `chrome.test.fail()`, and others,
+and it is often much more clear to avoid using `callbackPass()` and
+`callbackFail()`.  By comparison, having a single call to
+`chrome.test.succeed()` in an extension test function makes it more clear when
+success is met, and what the final operation is.  Additionally, there's no good
+way to sequence multiple operations - there is only a single internal "callback
+counter", which all callback-based utility methods share.  Finally, having
+multiple ways to signal a test is "done" leads to frequent developer and
+reviewer confusion and misuse (e.g., calling `chrome.test.succeed()` within a
+`callbackPass()` - see the [Do's And Don't's]).
+
+#### Alternatives
+##### Restructure the Test
+In the example above, it's unclear why the `storage.set()` call and
+`tabs.create()` call need to be within the same test.  They could instead be
+two separate test functions, passed serially in the array to
+`chrome.test.runTests()`.
+
+```js
+chrome.test.runTests([
+  function createTab() {
+    chrome.tabs.create({url: 'http://example.com'}, callbackPass(() => {
+      <verify state>
+      chrome.test.succeed();
+    }));
+  },
+  function initializeStorage() {
+    chrome.storage.local.set({foo: 'bar'}, callbackPass(() => {
+      <verify state>
+      chrome.test.succeed();
+    }));
+  },
+  function nextStep() {
+    ...
+  }
+]);
+```
+
+##### Use Promises
+The `chrome.test` callback mechanism was created before [Promises] were
+available as a language feature in JavaScript.  Promises can provide similar
+guarantees while providing more explicit direction to the reader about when a
+test will finish.
+
+```js
+chrome.test.runTests([
+  function step1() {
+    let tabPromise = new Promise((resolve) => {
+      chrome.tabs.create({url: 'http://example.com'}, () => {
+        <verify state>
+        resolve();
+      }));
+    });
+    let storagePromise = new Promise((resolve) => {
+      chrome.storage.local.set({foo: 'bar'}, () => {
+        <verify state>
+        resolve();
+      }));
+    });
+    Promise.all([tabPromise, storagePromise]).then(() => {
+      chrome.test.succeed();
+    });
+  },
+  function step2() {
+    ...
+  }
+]);
+```
+
+This solution is somewhat more verbose, but makes it more clear on which
+criteria the test is waiting, and can allow for different "sequences" of
+waiting.  This will also be much more streamlined when test APIs themselves can
+return promises.
+
+##### Use Async Functions
+The `chrome.test.runTests()` allows for [asynchronous functions] and using the
+[await] keyword. In conjunction with the Extension system's asynchronous APIs,
+this can lead to reasonably concise and readable code, such as the example
+below:
+
+```js
+const tab =
+    await new Promise(resolve => chrome.tabs.create({url: url}, resolve));
+<verify state>
+```
+
+This, too, will be even more readable with Promise-based APIs.
+
+### listenOnce() and listenForever()
+`chrome.test.listenOnce()` and `chrome.test.listenForever()` are utility
+functions used when waiting on different events.  Like `callbackPass()` and
+`callbackFail()` above, they use the test API's internal callback counter,
+allowing the test to finish automatically once all callbacks have been invoked.
+Naturally, they share all the same disadvantages as well.
+
+`listenOnce()` waits for the event to be invoked a single time, and then
+removes the listener and reduces the internal callback counter.  Calling
+`listenForever()` adds the listener and returns a function to be invoked at any
+point; invoking this function will remove the listener and decrement the
+callback counter.
+
+### getConfig()
+`chrome.test.getConfig()` retrieves the current configuration of the test
+setup.  This may be necessary for a variety of reasons - a common one is
+needing the port on which the test server is running in order to construct
+URLs, as below.
+
+```js
+chrome.test.getConfig((config) => {
+  let url = `http://example.com:${config.port}/simple.html`;
+  createTab(url);
+});
+```
+
+### sendMessage()
+`chrome.test.sendMessage()` is used to communicate with the C++ side of the
+browser test, allowing us to force synchronicity if necessary.  It should be
+used with [ExtensionTestMessageListener] on the C++ side.
+
+```c++
+// test.cc:
+IN_PROC_BROWSER_TEST_F(...) {
+  LoadExtension(...);
+  GURL url = GetASpecialURL();
+  ExtensionTestMessageListener listener("clicked", /*will_reply*/=true);
+  ClickAction();
+  ASSERT_TRUE(listener.WaitUntilSatisfied());
+  listener.reply(url.spec());
+  ...
+}
+```
+
+```js
+// extension_script.js:
+chrome.action.onClicked.addListener(() => {
+  chrome.test.sendMessage('clicked', (specialUrl) => {
+    useSpecialUrl(specialUrl);
+  });
+});
+```
+
+## Do's and Don't's
+### **Do** Write Small, "B[i|y]te-Size" Tests
+One of the advantages of having the `chrome.test.runTests()` method and
+infrastructure is that we can write small, unit-test style tests without
+needing to pay the cost of an extra browser test execution (expensive) for
+each individual test case.  Because of this, we can write very targeted,
+easy-to-consume, understandable test cases, rather than a single behemoth test
+case.
+
+### **Do** Prefer More Specific Asserts
+`assertTrue()` and `assertFalse()` provide the least information in the logs
+("expected true, found false"-style messages).  Assert-style methods like
+`assertEq()`, `assertLastError()`, and `assertNoLastError()` can provide much
+more detail (such as the actual value for `assertEq()`, or the error for
+`assertNoLastError()`).
+
+### **Do** Use Modern(ish) JS
+Many tests were written years and years ago.  Modern JS practices, such as
+`let` and `const`, [arrow functions], [template literals], and more, generally
+increase readability.  Don't feel shy about using them just because other
+examples don't.  (The caveat to this is that these tests should not use
+anything that isn't [approved](/styleguide/web/es.md) for Chromium JS use,
+unless the use of it is explicitly necessary for the test.)
+
+### **Don't** Mix chrome.test.notifyPass() and chrome.test.runTests()
+`chrome.test.notifyPass()` (or `chrome.test.notifyFail()`) will finish the
+entire suite.  It should not be used with `chrome.test.runTests()`.  Instead,
+use chrome.test.succeed().
+
+### **Don't** Mix chrome.test.callbackPass() et al. and chrome.test.succeed()
+If the callback counter is incremented anywhere in a test function, the test
+infrastructure will automatically invoke `chrome.test.succeed()` when the
+counter reaches zero.  Calling `chrome.test.succeed()` in addition to
+`callbackPass()`, `callbackFail()`, `listenOnce()`, or `listenForever()` can
+result in unpredictable behavior, or masking failures.
+
+### **Don't** Call other aysnchronous functions after callbackPass/Fail()
+Consider the following code:
+
+```js
+chrome.foo.asyncFunction(..., callbackPass(() => {
+  chrome.foo.asyncFunction2(..., () => {
+    // Extra stuff
+  });
+});
+```
+
+In this case, the callback counter will wait for the call to `asyncFunction()`
+to complete and execute the inner function, and will then immediately end the
+test case. The callback from `asyncFunction2()` will be invoked after the test
+case is over, which can lead to flakiness or false-negatives.
+
+If you have to have nested functions, each should use `callbackPass()` or
+`callbackFail()`. Better yet, migrate away from `callbackPass()` and
+`callbackFail()` with [these alternatives](#Alternatives).
+
+### **Don't** Have multiple "win" conditions
+While it's possible to have multiple `notifyPass()` calls in a single C++
+browser test (by using multiple calls to `RunExtensionTest()` or using multiple
+`ResultCatchers`), and by extension possible to mix `runTests()` with
+`notifyPass()` with `sendMessage()` with everything else, it's generally a bad
+idea.  It makes the flow of control incredibly difficult to parse for author,
+reviewer, and future reader, and frequently leads to subtle bugs being missed.
+
+Instead, if a complex chain of steps is needed, use either
+`chrome.test.runTests()` or `chrome.test.sendMessage()` (if C++ coordination is
+needed).
+
+### **Don't** Go overboard with runTests()
+It can be tempting to set up a sequence of a 20-step sequence in
+`chrome.test.runTests()`, but this makes it very difficult to understand the
+total flow, harder to debug, and increases the risk of timing out (from simply
+doing too much).  If a test relies on multiple steps in `runTests()`, have a
+dedicated browser test for those related steps, and restrict them to a
+reasonable number.  If a test has a series of unrelated steps, keep them small
+and targeted (in good unit testing behavior).
+
+[writing extension tests]: extension_tests.md
+[Do's And Don't's]: #Dos-and-Dont_s
+[Promises]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
+[asynchronous functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
+[await]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
+[ExtensionTestMessageListener]: ../test/extension_test_message_listener.h
+[arrow functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
+[template literals]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
diff --git a/extensions/renderer/api/automation/automation_internal_custom_bindings.cc b/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
index 6575001..a6e20c9 100644
--- a/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
+++ b/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
@@ -1715,9 +1715,12 @@
     return false;
 
   // If the focused node is the owner of a child tree, that indicates
-  // a node within the child tree is the one that actually has focus.
+  // a node within the child tree is the one that actually has focus. This
+  // doesn't apply to portals: portals have a child tree, but nothing in the
+  // tree can have focus.
   while (focus->data().HasStringAttribute(
-      ax::mojom::StringAttribute::kChildTreeId)) {
+             ax::mojom::StringAttribute::kChildTreeId) &&
+         focus->data().role != ax::mojom::Role::kPortal) {
     // Try to keep following focus recursively, by letting |tree_id| be the
     // new subtree to search in, while keeping |focus_tree_id| set to the tree
     // where we know we found a focused node.
diff --git a/extensions/renderer/resources/automation/automation_custom_bindings.js b/extensions/renderer/resources/automation/automation_custom_bindings.js
index 255d032..84d1b1c 100644
--- a/extensions/renderer/resources/automation/automation_custom_bindings.js
+++ b/extensions/renderer/resources/automation/automation_custom_bindings.js
@@ -190,7 +190,11 @@
   // browser process and set up a callback when it loads to attach that
   // tree as a child of this node and fire appropriate events.
   automationUtil.storeTreeCallback(childTreeId, function(root) {
-    privates(root).impl.dispatchEvent('loadComplete', 'page');
+    const rootImpl = privates(root).impl;
+    rootImpl.dispatchEvent('loadComplete', 'page');
+    if (rootImpl.parent) {
+      privates(rootImpl.parent).impl.dispatchEvent('childrenChanged');
+    }
   }, true);
 
   automationInternal.enableTree(childTreeId);
diff --git a/extensions/renderer/resources/automation/automation_node.js b/extensions/renderer/resources/automation/automation_node.js
index 7720b3d..6676645 100644
--- a/extensions/renderer/resources/automation/automation_node.js
+++ b/extensions/renderer/resources/automation/automation_node.js
@@ -1240,8 +1240,9 @@
     'value'];
 
 var boolAttributes = [
-  'busy', 'clickable', 'containerLiveAtomic', 'containerLiveBusy', 'liveAtomic',
-  'modal', 'scrollable', 'selected', 'supportsTextLocation'
+  'busy', 'clickable', 'containerLiveAtomic', 'containerLiveBusy',
+  'editableRoot', 'liveAtomic', 'modal', 'scrollable', 'selected',
+  'supportsTextLocation'
 ];
 
 var intAttributes = [
diff --git a/fuchsia/engine/web_engine_integration_test.cc b/fuchsia/engine/web_engine_integration_test.cc
index 0eae63e..e963f2d 100644
--- a/fuchsia/engine/web_engine_integration_test.cc
+++ b/fuchsia/engine/web_engine_integration_test.cc
@@ -27,7 +27,9 @@
 #include "fuchsia/base/test_navigation_listener.h"
 #include "media/base/media_switches.h"
 #include "media/fuchsia/audio/fake_audio_consumer.h"
+#include "net/base/test_completion_callback.h"
 #include "net/http/http_request_headers.h"
+#include "net/socket/tcp_client_socket.h"
 #include "net/test/embedded_test_server/default_handlers.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -263,8 +265,7 @@
 // Check that if the CreateContextParams has |remote_debugging_port| set then:
 // - DevTools becomes available when the first debuggable Frame is created.
 // - DevTools closes when the last debuggable Frame is closed.
-// Test is flaky. http://crbug.com/1067727
-TEST_F(WebEngineIntegrationTest, DISABLED_RemoteDebuggingPort) {
+TEST_F(WebEngineIntegrationTest, RemoteDebuggingPort) {
   StartWebEngine();
 
   // Create a Context with remote debugging enabled via an ephemeral port.
@@ -356,8 +357,32 @@
   // handled the Frame tear down.
   controller_run_loop.Run();
 
-  devtools_list = cr_fuchsia::GetDevToolsListFromPort(remote_debugging_port);
-  EXPECT_TRUE(devtools_list.is_none());
+  // Verify that devtools server is shut down properly. WebEngine may shutdown
+  // the socket after shutting down the Frame, so make several attempts to
+  // connect until it fails. Don't try to read or write from/to the socket to
+  // avoid fxb/49779.
+  bool failed_to_connect = false;
+  for (int i = 0; i < 10; ++i) {
+    net::TestCompletionCallback connect_callback;
+    net::TCPClientSocket connecting_socket(
+        net::AddressList(net::IPEndPoint(net::IPAddress::IPv4Localhost(),
+                                         remote_debugging_port)),
+        nullptr, nullptr, net::NetLogSource());
+    int connect_result = connecting_socket.Connect(connect_callback.callback());
+    connect_result = connect_callback.GetResult(connect_result);
+
+    if (connect_result == net::OK) {
+      // If Connect() succeeded then try again a bit later.
+      base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+      continue;
+    }
+
+    EXPECT_EQ(connect_result, net::ERR_CONNECTION_REFUSED);
+    failed_to_connect = true;
+    break;
+  }
+
+  EXPECT_TRUE(failed_to_connect);
 }
 
 // Check that remote debugging requests for Frames in non-debuggable Contexts
diff --git a/google_apis/gaia/oauth2_access_token_manager.cc b/google_apis/gaia/oauth2_access_token_manager.cc
index 434f18e..21afd8e5 100644
--- a/google_apis/gaia/oauth2_access_token_manager.cc
+++ b/google_apis/gaia/oauth2_access_token_manager.cc
@@ -14,6 +14,13 @@
 #include "google_apis/gaia/oauth2_access_token_fetcher.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
+namespace {
+void RecordOAuth2TokenFetchResult(GoogleServiceAuthError::State state) {
+  UMA_HISTOGRAM_ENUMERATION("Signin.OAuth2TokenGetResult", state,
+                            GoogleServiceAuthError::NUM_STATES);
+}
+}  // namespace
+
 int OAuth2AccessTokenManager::max_fetch_retry_num_ = 5;
 
 OAuth2AccessTokenManager::Delegate::Delegate() = default;
@@ -275,6 +282,8 @@
     const OAuth2AccessTokenConsumer::TokenResponse& token_response) {
   fetcher_.reset();
 
+  RecordOAuth2TokenFetchResult(GoogleServiceAuthError::NONE);
+
   // Fetch completes.
   error_ = GoogleServiceAuthError::AuthErrorNone();
   token_response_ = token_response;
@@ -295,8 +304,8 @@
   if (ShouldRetry(error) && RetryIfPossible(error))
     return;
 
-  UMA_HISTOGRAM_ENUMERATION("Signin.OAuth2TokenGetFailure", error.state(),
-                            GoogleServiceAuthError::NUM_STATES);
+  RecordOAuth2TokenFetchResult(error.state());
+
   error_ = error;
   InformWaitingRequestsAndDelete();
 }
diff --git a/gpu/command_buffer/common/BUILD.gn b/gpu/command_buffer/common/BUILD.gn
index 155fa3f..bcf81ea 100644
--- a/gpu/command_buffer/common/BUILD.gn
+++ b/gpu/command_buffer/common/BUILD.gn
@@ -181,7 +181,7 @@
 
   public_deps = [
     ":common",
-    "//third_party/dawn/src/dawn_wire:libdawn_wire_headers",
+    "//third_party/dawn/src/dawn_wire:dawn_wire_headers",
     "//ui/gl:buildflags",
   ]
 }
diff --git a/gpu/command_buffer/service/scheduler.cc b/gpu/command_buffer/service/scheduler.cc
index c2e7112..2eb62b7 100644
--- a/gpu/command_buffer/service/scheduler.cc
+++ b/gpu/command_buffer/service/scheduler.cc
@@ -188,15 +188,18 @@
 
 void Scheduler::Sequence::AddWaitFence(const SyncToken& sync_token,
                                        uint32_t order_num,
-                                       SequenceId release_sequence_id,
-                                       Sequence* release_sequence) {
+                                       SequenceId release_sequence_id) {
   auto it =
       wait_fences_.find(WaitFence{sync_token, order_num, release_sequence_id});
   if (it != wait_fences_.end())
     return;
 
-  DCHECK(release_sequence);
-  release_sequence->AddWaitingPriority(default_priority_);
+  // |release_sequence| can be nullptr if we wait on SyncToken from sequence
+  // that is not in this scheduler. It can happen on WebView when compositing
+  // that runs on different thread returns resources.
+  Sequence* release_sequence = scheduler_->GetSequence(release_sequence_id);
+  if (release_sequence)
+    release_sequence->AddWaitingPriority(default_priority_);
 
   wait_fences_.emplace(
       std::make_pair(WaitFence(sync_token, order_num, release_sequence_id),
@@ -393,16 +396,12 @@
   for (const SyncToken& sync_token : task.sync_token_fences) {
     SequenceId release_sequence_id =
         sync_point_manager_->GetSyncTokenReleaseSequenceId(sync_token);
-    Sequence* release_sequence = GetSequence(release_sequence_id);
-    if (!release_sequence)
-      continue;
     if (sync_point_manager_->WaitNonThreadSafe(
             sync_token, sequence_id, order_num, task_runner_,
             base::BindOnce(&Scheduler::SyncTokenFenceReleased, weak_ptr_,
                            sync_token, order_num, release_sequence_id,
                            sequence_id))) {
-      sequence->AddWaitFence(sync_token, order_num, release_sequence_id,
-                             release_sequence);
+      sequence->AddWaitFence(sync_token, order_num, release_sequence_id);
     }
   }
 
diff --git a/gpu/command_buffer/service/scheduler.h b/gpu/command_buffer/service/scheduler.h
index 0838d70..4ea67121 100644
--- a/gpu/command_buffer/service/scheduler.h
+++ b/gpu/command_buffer/service/scheduler.h
@@ -186,8 +186,7 @@
     // Add a sync token fence that this sequence should wait on.
     void AddWaitFence(const SyncToken& sync_token,
                       uint32_t order_num,
-                      SequenceId release_sequence_id,
-                      Sequence* release_sequence);
+                      SequenceId release_sequence_id);
 
     // Remove a waiting sync token fence.
     void RemoveWaitFence(const SyncToken& sync_token,
diff --git a/gpu/command_buffer/service/scheduler_unittest.cc b/gpu/command_buffer/service/scheduler_unittest.cc
index ff5962d..7202a8a 100644
--- a/gpu/command_buffer/service/scheduler_unittest.cc
+++ b/gpu/command_buffer/service/scheduler_unittest.cc
@@ -103,9 +103,8 @@
  public:
   SchedulerTaskRunOrderTest() = default;
   ~SchedulerTaskRunOrderTest() override {
-    for (auto info_it : sequence_info_) {
-      info_it.second.release_state->Destroy();
-      scheduler()->DestroySequence(info_it.second.sequence_id);
+    while (!sequence_info_.empty()) {
+      DestroySequence(sequence_info_.begin()->first);
     }
   }
 
@@ -123,12 +122,26 @@
         SequenceInfo(sequence_id, command_buffer_id, release_state)));
   }
 
+  void CreateExternalSequence(int sequence_key) {
+    auto order_data = sync_point_manager()->CreateSyncPointOrderData();
+    auto command_buffer_id = CommandBufferId::FromUnsafeValue(sequence_key);
+    auto release_state = sync_point_manager()->CreateSyncPointClientState(
+        kNamespaceId, command_buffer_id, order_data->sequence_id());
+
+    sequence_info_.emplace(std::make_pair(
+        sequence_key,
+        SequenceInfo(std::move(order_data), command_buffer_id, release_state)));
+  }
+
   void DestroySequence(int sequence_key) {
     auto info_it = sequence_info_.find(sequence_key);
     ASSERT_TRUE(info_it != sequence_info_.end());
 
     info_it->second.release_state->Destroy();
-    scheduler()->DestroySequence(info_it->second.sequence_id);
+    if (info_it->second.order_data)
+      info_it->second.order_data->Destroy();
+    else
+      scheduler()->DestroySequence(info_it->second.sequence_id);
 
     sequence_info_.erase(info_it);
   }
@@ -143,13 +156,16 @@
         SyncToken(kNamespaceId, info_it->second.command_buffer_id, release)));
   }
 
-  void ScheduleTask(int sequence_key, int wait_sync, int release_sync) {
-    const int task_id = num_tasks_scheduled_++;
+  static void RunExternalTask(base::OnceClosure task,
+                              scoped_refptr<SyncPointOrderData> order_data,
+                              uint32_t order_num) {
+    order_data->BeginProcessingOrderNumber(order_num);
+    std::move(task).Run();
+    order_data->FinishProcessingOrderNumber(order_num);
+  }
 
-    std::vector<SyncToken> wait;
-    if (wait_sync >= 0) {
-      wait.push_back(sync_tokens_[wait_sync]);
-    }
+  base::OnceClosure GetTaskClosure(int sequence_key, int release_sync) {
+    const int task_id = num_tasks_scheduled_++;
 
     uint64_t release = 0;
     if (release_sync >= 0) {
@@ -158,19 +174,45 @@
     }
 
     auto info_it = sequence_info_.find(sequence_key);
+    DCHECK(info_it != sequence_info_.end());
+
+    auto closure = GetClosure([this, task_id, sequence_key, release] {
+      if (release) {
+        auto info_it = sequence_info_.find(sequence_key);
+        ASSERT_TRUE(info_it != sequence_info_.end());
+        info_it->second.release_state->ReleaseFenceSync(release);
+      }
+      this->tasks_executed_.push_back(task_id);
+    });
+
+    // Simulate external sequence, when tasks are run outside of this
+    // gpu::Scheduler
+    if (info_it->second.external()) {
+      auto order_data = info_it->second.order_data;
+      uint32_t order_num = order_data->GenerateUnprocessedOrderNumber();
+
+      return base::BindOnce(RunExternalTask, std::move(closure), order_data,
+                            order_num);
+    } else {
+      return closure;
+    }
+  }
+
+  void ScheduleTask(int sequence_key, int wait_sync, int release_sync) {
+    auto closure = GetTaskClosure(sequence_key, release_sync);
+
+    auto info_it = sequence_info_.find(sequence_key);
     ASSERT_TRUE(info_it != sequence_info_.end());
 
-    scheduler()->ScheduleTask(Scheduler::Task(
-        info_it->second.sequence_id,
-        GetClosure([this, task_id, sequence_key, release] {
-          if (release) {
-            auto info_it = sequence_info_.find(sequence_key);
-            ASSERT_TRUE(info_it != sequence_info_.end());
-            info_it->second.release_state->ReleaseFenceSync(release);
-          }
-          this->tasks_executed_.push_back(task_id);
-        }),
-        wait));
+    DCHECK(!info_it->second.external());
+
+    std::vector<SyncToken> wait;
+    if (wait_sync >= 0) {
+      wait.push_back(sync_tokens_[wait_sync]);
+    }
+
+    scheduler()->ScheduleTask(
+        Scheduler::Task(info_it->second.sequence_id, std::move(closure), wait));
   }
 
   void RunAllPendingTasks() {
@@ -193,8 +235,20 @@
           command_buffer_id(command_buffer_id),
           release_state(release_state) {}
 
+    SequenceInfo(scoped_refptr<SyncPointOrderData> order_data,
+                 CommandBufferId command_buffer_id,
+                 scoped_refptr<SyncPointClientState> release_state)
+        : sequence_id(order_data->sequence_id()),
+          command_buffer_id(command_buffer_id),
+          order_data(order_data),
+          release_state(release_state) {}
+
+    bool external() const { return !!order_data; }
+
     SequenceId sequence_id;
     CommandBufferId command_buffer_id;
+    // |order_data| is only set for external sequences.
+    scoped_refptr<SyncPointOrderData> order_data;
     scoped_refptr<SyncPointClientState> release_state;
   };
 
@@ -249,6 +303,23 @@
   EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
 }
 
+TEST_F(SchedulerTaskRunOrderTest, SequenceWaitsForFenceExternal) {
+  CreateSequence(0, SchedulingPriority::kHigh);
+  CreateExternalSequence(1);
+
+  // Create task 0 on seq 1 that will release 0, but don't post it.
+  auto external_task = GetTaskClosure(1, 0);
+
+  ScheduleTask(0, 0, -1);  // task 1: seq 0, wait 0, no release
+
+  task_runner()->PostTask(FROM_HERE, std::move(external_task));
+
+  RunAllPendingTasks();
+
+  const int expected_task_order[] = {0, 1};
+  EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
+}
+
 TEST_F(SchedulerTaskRunOrderTest, SequenceDoesNotWaitForInvalidFence) {
   CreateSequence(0, SchedulingPriority::kNormal);
   CreateSequence(1, SchedulingPriority::kNormal);
@@ -541,17 +612,17 @@
   SyncToken sync_token2(namespace_id, command_buffer_id2, 1);
 
   // Wait priorities propagate.
-  seq2->AddWaitFence(sync_token1, 1, seq_id1, seq1);
+  seq2->AddWaitFence(sync_token1, 1, seq_id1);
   EXPECT_EQ(SchedulingPriority::kNormal, seq1->current_priority());
   EXPECT_EQ(SchedulingPriority::kNormal, seq2->current_priority());
   EXPECT_EQ(SchedulingPriority::kHigh, seq3->current_priority());
 
-  seq2->AddWaitFence(sync_token1, 1, seq_id1, seq1);
+  seq2->AddWaitFence(sync_token1, 1, seq_id1);
   EXPECT_EQ(SchedulingPriority::kNormal, seq1->current_priority());
   EXPECT_EQ(SchedulingPriority::kNormal, seq2->current_priority());
   EXPECT_EQ(SchedulingPriority::kHigh, seq3->current_priority());
 
-  seq3->AddWaitFence(sync_token2, 2, seq_id2, seq2);
+  seq3->AddWaitFence(sync_token2, 2, seq_id2);
   EXPECT_EQ(SchedulingPriority::kHigh, seq1->current_priority());
   EXPECT_EQ(SchedulingPriority::kHigh, seq2->current_priority());
   EXPECT_EQ(SchedulingPriority::kHigh, seq3->current_priority());
@@ -599,9 +670,9 @@
   SyncToken sync_token2(namespace_id, command_buffer_id2, 1);
 
   // Wait priorities propagate.
-  seq2->AddWaitFence(sync_token1, 1, seq_id1, seq1);
-  seq2->AddWaitFence(sync_token1, 1, seq_id1, seq1);
-  seq3->AddWaitFence(sync_token2, 2, seq_id2, seq2);
+  seq2->AddWaitFence(sync_token1, 1, seq_id1);
+  seq2->AddWaitFence(sync_token1, 1, seq_id1);
+  seq3->AddWaitFence(sync_token2, 2, seq_id2);
 
   EXPECT_EQ(SchedulingPriority::kHigh, seq1->current_priority());
   EXPECT_EQ(SchedulingPriority::kHigh, seq2->current_priority());
@@ -649,8 +720,8 @@
   SyncToken sync_token2(namespace_id, command_buffer_id2, 2);
 
   // Wait on same fence multiple times.
-  seq2->AddWaitFence(sync_token1, 1, seq_id1, seq1);
-  seq2->AddWaitFence(sync_token1, 1, seq_id1, seq1);
+  seq2->AddWaitFence(sync_token1, 1, seq_id1);
+  seq2->AddWaitFence(sync_token1, 1, seq_id1);
 
   EXPECT_EQ(SchedulingPriority::kNormal, seq1->current_priority());
   EXPECT_EQ(SchedulingPriority::kNormal, seq2->current_priority());
@@ -663,7 +734,7 @@
   EXPECT_EQ(SchedulingPriority::kHigh, seq3->current_priority());
 
   // Add wait fence with higher priority.  This replicates a possible race.
-  seq3->AddWaitFence(sync_token2, 2, seq_id2, seq2);
+  seq3->AddWaitFence(sync_token2, 2, seq_id2);
   EXPECT_EQ(SchedulingPriority::kLow, seq1->current_priority());
   EXPECT_EQ(SchedulingPriority::kHigh, seq2->current_priority());
   EXPECT_EQ(SchedulingPriority::kHigh, seq3->current_priority());
@@ -711,22 +782,22 @@
   SyncToken sync_token_seq2_3(namespace_id, command_buffer_id2, 3);
   SyncToken sync_token_seq3_1(namespace_id, command_buffer_id3, 1);
 
-  seq3->AddWaitFence(sync_token_seq2_1, 1, seq_id2, seq2);
+  seq3->AddWaitFence(sync_token_seq2_1, 1, seq_id2);
   EXPECT_EQ(SchedulingPriority::kHigh, seq1->current_priority());
   EXPECT_EQ(SchedulingPriority::kNormal, seq2->current_priority());
   EXPECT_EQ(SchedulingPriority::kNormal, seq3->current_priority());
 
-  seq1->AddWaitFence(sync_token_seq2_2, 2, seq_id2, seq2);
+  seq1->AddWaitFence(sync_token_seq2_2, 2, seq_id2);
   EXPECT_EQ(SchedulingPriority::kHigh, seq1->current_priority());
   EXPECT_EQ(SchedulingPriority::kHigh, seq2->current_priority());
   EXPECT_EQ(SchedulingPriority::kNormal, seq3->current_priority());
 
-  seq3->AddWaitFence(sync_token_seq2_3, 3, seq_id2, seq2);
+  seq3->AddWaitFence(sync_token_seq2_3, 3, seq_id2);
   EXPECT_EQ(SchedulingPriority::kHigh, seq1->current_priority());
   EXPECT_EQ(SchedulingPriority::kHigh, seq2->current_priority());
   EXPECT_EQ(SchedulingPriority::kNormal, seq3->current_priority());
 
-  seq2->AddWaitFence(sync_token_seq3_1, 4, seq_id3, seq3);
+  seq2->AddWaitFence(sync_token_seq3_1, 4, seq_id3);
   EXPECT_EQ(SchedulingPriority::kHigh, seq1->current_priority());
   EXPECT_EQ(SchedulingPriority::kHigh, seq2->current_priority());
   EXPECT_EQ(SchedulingPriority::kNormal, seq3->current_priority());
diff --git a/gpu/command_buffer/service/shared_image_backing_ozone.cc b/gpu/command_buffer/service/shared_image_backing_ozone.cc
index b95907a..c5686c3 100644
--- a/gpu/command_buffer/service/shared_image_backing_ozone.cc
+++ b/gpu/command_buffer/service/shared_image_backing_ozone.cc
@@ -65,6 +65,30 @@
 
 }  // namespace
 
+class SharedImageBackingOzone::SharedImageRepresentationVaapiOzone
+    : public SharedImageRepresentationVaapi {
+ public:
+  SharedImageRepresentationVaapiOzone(SharedImageManager* manager,
+                                      SharedImageBacking* backing,
+                                      MemoryTypeTracker* tracker,
+                                      VaapiDependencies* vaapi_dependency)
+      : SharedImageRepresentationVaapi(manager,
+                                       backing,
+                                       tracker,
+                                       vaapi_dependency) {}
+
+ private:
+  SharedImageBackingOzone* ozone_backing() {
+    return static_cast<SharedImageBackingOzone*>(backing());
+  }
+  void EndAccess() override { ozone_backing()->has_pending_va_writes_ = true; }
+  void BeginAccess() override {
+    // TODO(andrescj): DCHECK that there are no fences to wait on (because the
+    // compositor should be completely done with a VideoFrame before returning
+    // it).
+  }
+};
+
 std::unique_ptr<SharedImageBackingOzone> SharedImageBackingOzone::Create(
     scoped_refptr<base::RefCountedData<DawnProcTable>> dawn_procs,
     SharedContextState* context_state,
@@ -90,7 +114,6 @@
   if (!pixmap) {
     return nullptr;
   }
-
   return base::WrapUnique(new SharedImageBackingOzone(
       mailbox, format, size, color_space, usage, context_state,
       std::move(pixmap), std::move(dawn_procs)));
@@ -174,24 +197,6 @@
   return nullptr;
 }
 
-std::unique_ptr<SharedImageRepresentationVaapi>
-SharedImageBackingOzone::ProduceVASurface(
-    SharedImageManager* manager,
-    MemoryTypeTracker* tracker,
-    VaapiDependenciesFactory* dep_factory) {
-  DCHECK(pixmap_);
-  if (!vaapi_deps_)
-    vaapi_deps_ = dep_factory->CreateVaapiDependencies(pixmap_);
-
-  if (!vaapi_deps_) {
-    LOG(ERROR) << "SharedImageBackingOzone::ProduceVASurface failed to create "
-                  "VaapiDependencies";
-    return nullptr;
-  }
-  return std::make_unique<SharedImageRepresentationVaapi>(
-      manager, this, tracker, vaapi_deps_.get());
-}
-
 SharedImageBackingOzone::SharedImageBackingOzone(
     const Mailbox& mailbox,
     viz::ResourceFormat format,
@@ -211,4 +216,28 @@
       pixmap_(std::move(pixmap)),
       dawn_procs_(std::move(dawn_procs)) {}
 
+std::unique_ptr<SharedImageRepresentationVaapi>
+SharedImageBackingOzone::ProduceVASurface(
+    SharedImageManager* manager,
+    MemoryTypeTracker* tracker,
+    VaapiDependenciesFactory* dep_factory) {
+  DCHECK(pixmap_);
+  if (!vaapi_deps_)
+    vaapi_deps_ = dep_factory->CreateVaapiDependencies(pixmap_);
+
+  if (!vaapi_deps_) {
+    LOG(ERROR) << "SharedImageBackingOzone::ProduceVASurface failed to create "
+                  "VaapiDependencies";
+    return nullptr;
+  }
+  return std::make_unique<
+      SharedImageBackingOzone::SharedImageRepresentationVaapiOzone>(
+      manager, this, tracker, vaapi_deps_.get());
+}
+
+bool SharedImageBackingOzone::VaSync() {
+  if (has_pending_va_writes_)
+    has_pending_va_writes_ = !vaapi_deps_->SyncSurface();
+  return !has_pending_va_writes_;
+}
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image_backing_ozone.h b/gpu/command_buffer/service/shared_image_backing_ozone.h
index 0ec9c2da..e49d259 100644
--- a/gpu/command_buffer/service/shared_image_backing_ozone.h
+++ b/gpu/command_buffer/service/shared_image_backing_ozone.h
@@ -73,6 +73,10 @@
       VaapiDependenciesFactory* dep_factory) override;
 
  private:
+  friend class SharedImageRepresentationGLOzone;
+  friend class SharedImageRepresentationDawnOzone;
+  class SharedImageRepresentationVaapiOzone;
+
   SharedImageBackingOzone(
       const Mailbox& mailbox,
       viz::ResourceFormat format,
@@ -83,6 +87,10 @@
       scoped_refptr<gfx::NativePixmap> pixmap,
       scoped_refptr<base::RefCountedData<DawnProcTable>> dawn_procs);
 
+  bool VaSync();
+
+  // Indicates if this backing produced a VASurface that may have pending work.
+  bool has_pending_va_writes_ = false;
   std::unique_ptr<VaapiDependencies> vaapi_deps_;
   scoped_refptr<gfx::NativePixmap> pixmap_;
   scoped_refptr<base::RefCountedData<DawnProcTable>> dawn_procs_;
diff --git a/gpu/command_buffer/service/shared_image_representation.h b/gpu/command_buffer/service/shared_image_representation.h
index 391d3c66..ceb49c2 100644
--- a/gpu/command_buffer/service/shared_image_representation.h
+++ b/gpu/command_buffer/service/shared_image_representation.h
@@ -390,6 +390,7 @@
  public:
   virtual ~VaapiDependencies() = default;
   virtual const media::VASurface* GetVaSurface() const = 0;
+  virtual bool SyncSurface() = 0;
 };
 
 // Interface that allows a SharedImageBacking to create VaapiDependencies from a
@@ -416,9 +417,6 @@
 // decoding work until we're sure that the destination buffer is not being used
 // by the rest of the pipeline. However, we still need to keep track of write
 // accesses so that other representations can synchronize with the decoder.
-//
-// TODO(pwarren): create subclass SharedImageRepresentationVaapiOzone that
-// implements EndAccess.
 class GPU_GLES2_EXPORT SharedImageRepresentationVaapi
     : public SharedImageRepresentation {
  public:
@@ -442,8 +440,8 @@
 
  private:
   VaapiDependencies* vaapi_deps_;
-  // TODO(pwarren): Make EndAccess purely virtual.
-  virtual void EndAccess() {}
+  virtual void EndAccess() = 0;
+  virtual void BeginAccess() = 0;
 };
 
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image_representation_dawn_ozone.cc b/gpu/command_buffer/service/shared_image_representation_dawn_ozone.cc
index f8d6e81..d134bf3 100644
--- a/gpu/command_buffer/service/shared_image_representation_dawn_ozone.cc
+++ b/gpu/command_buffer/service/shared_image_representation_dawn_ozone.cc
@@ -50,6 +50,9 @@
   if (texture_) {
     return nullptr;
   }
+  if (!ozone_backing()->VaSync()) {
+    return nullptr;
+  }
   DCHECK(pixmap_->GetNumberOfPlanes() == 1)
       << "Multi-plane formats are not supported.";
   // TODO(hob): Synchronize access to the dma-buf by waiting on all semaphores
diff --git a/gpu/command_buffer/service/shared_image_representation_dawn_ozone.h b/gpu/command_buffer/service/shared_image_representation_dawn_ozone.h
index 5269612..6fa1145 100644
--- a/gpu/command_buffer/service/shared_image_representation_dawn_ozone.h
+++ b/gpu/command_buffer/service/shared_image_representation_dawn_ozone.h
@@ -12,6 +12,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.h"
 #include "gpu/command_buffer/service/shared_image_backing.h"
+#include "gpu/command_buffer/service/shared_image_backing_ozone.h"
 #include "gpu/command_buffer/service/shared_image_manager.h"
 #include "gpu/command_buffer/service/shared_image_representation.h"
 #include "ui/gfx/native_pixmap.h"
@@ -40,6 +41,11 @@
   void EndAccess() override;
 
  private:
+  // TODO(andrescj): move other shared image representations into
+  // SharedImageBackingOzone.
+  SharedImageBackingOzone* ozone_backing() {
+    return static_cast<SharedImageBackingOzone*>(backing());
+  }
   const WGPUDevice device_;
   const WGPUTextureFormat format_;
   scoped_refptr<gfx::NativePixmap> pixmap_;
diff --git a/gpu/command_buffer/service/shared_image_representation_gl_ozone.cc b/gpu/command_buffer/service/shared_image_representation_gl_ozone.cc
index 8cf19ec..c9317789 100644
--- a/gpu/command_buffer/service/shared_image_representation_gl_ozone.cc
+++ b/gpu/command_buffer/service/shared_image_representation_gl_ozone.cc
@@ -100,11 +100,10 @@
 bool SharedImageRepresentationGLOzone::BeginAccess(GLenum mode) {
   // TODO(hob): Synchronize access to the dma-buf by waiting on all semaphores
   // tracked by SharedImageBackingOzone.
-  //
-  // TODO(pwarren): When this representation is involved with a VA-API decode,
-  // we must call VaapiWrapper::SyncSurface() to ensure all VA-API work is done
+
+  // We must call VaapiWrapper::SyncSurface() to ensure all VA-API work is done
   // prior to using the buffer in a graphics API.
-  return true;
+  return ozone_backing()->VaSync();
 }
 
 void SharedImageRepresentationGLOzone::EndAccess() {
diff --git a/gpu/command_buffer/service/shared_image_representation_gl_ozone.h b/gpu/command_buffer/service/shared_image_representation_gl_ozone.h
index b5ebe1b..40ba7822 100644
--- a/gpu/command_buffer/service/shared_image_representation_gl_ozone.h
+++ b/gpu/command_buffer/service/shared_image_representation_gl_ozone.h
@@ -10,6 +10,7 @@
 #include "components/viz/common/resources/resource_format.h"
 #include "gpu/command_buffer/service/memory_tracking.h"
 #include "gpu/command_buffer/service/shared_image_backing.h"
+#include "gpu/command_buffer/service/shared_image_backing_ozone.h"
 #include "gpu/command_buffer/service/shared_image_manager.h"
 #include "gpu/command_buffer/service/shared_image_representation.h"
 #include "gpu/command_buffer/service/texture_manager.h"
@@ -45,6 +46,9 @@
                                    gles2::Texture* texture);
 
   gles2::Texture* texture_;
+  SharedImageBackingOzone* ozone_backing() {
+    return static_cast<SharedImageBackingOzone*>(backing());
+  }
 
   DISALLOW_COPY_AND_ASSIGN(SharedImageRepresentationGLOzone);
 };
diff --git a/infra/config/buckets/ci.star b/infra/config/buckets/ci.star
index a8ae257..60e543f 100644
--- a/infra/config/buckets/ci.star
+++ b/infra/config/buckets/ci.star
@@ -33,6 +33,45 @@
 )
 
 ci.console_view(
+    name = 'chromium.android',
+    ordering = {
+        None: ['cronet', 'builder', 'tester'],
+        '*cpu*': ['arm', 'arm64', 'x86'],
+        'cronet': '*cpu*',
+        'builder': '*cpu*',
+        'builder|det': ci.ordering(short_names=['rel', 'dbg']),
+        'tester': ['phone', 'tablet'],
+        'builder_tester|arm64': ci.ordering(short_names=['M proguard']),
+    },
+)
+
+ci.console_view(
+    name = 'chromium.chromiumos',
+    ordering = {
+        None: ['default'],
+        'default': ci.ordering(short_names=['ful', 'rel']),
+        'simple': ['release', 'debug'],
+    },
+)
+
+ci.console_view(
+    name = 'chromium.dawn',
+    ordering = {
+        None: ['ToT'],
+        '*builder*': ['Builder'],
+        '*cpu*': ci.ordering(short_names=['x86']),
+        'ToT|Mac': '*builder*',
+        'ToT|Windows|Builder': '*cpu*',
+        'ToT|Windows|Intel': '*cpu*',
+        'ToT|Windows|Nvidia': '*cpu*',
+        'DEPS|Mac': '*builder*',
+        'DEPS|Windows|Builder': '*cpu*',
+        'DEPS|Windows|Intel': '*cpu*',
+        'DEPS|Windows|Nvidia': '*cpu*',
+    },
+)
+
+ci.console_view(
     name = 'chromium.linux',
     ordering = {
         None: ['release', 'debug'],
@@ -52,6 +91,17 @@
 )
 
 ci.console_view(
+    name = 'chromium.memory',
+    ordering = {
+        None: ['win', 'mac', 'linux', 'cros'],
+        '*build-or-test*': ci.ordering(short_names=['bld', 'tst']),
+        'linux|TSan v2': '*build-or-test*',
+        'linux|asan lsan': '*build-or-test*',
+        'linux|webkit': ci.ordering(short_names=['asn', 'msn']),
+    },
+)
+
+ci.console_view(
     name = 'chromium.win',
     ordering = {
         None: ['release', 'debug'],
@@ -170,32 +220,56 @@
 
 ci.android_builder(
     name = 'Android ASAN (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'on_cq',
+        short_name = 'san',
+    ),
 )
 
 ci.android_builder(
     name = 'Android WebView L (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|webview',
+        short_name = 'L',
+    ),
     triggered_by = ['ci/Android arm Builder (dbg)'],
 )
 
 ci.android_builder(
     name = 'Deterministic Android',
+    console_view_entry = ci.console_view_entry(
+        category = 'builder|det',
+        short_name = 'rel',
+    ),
     executable = 'recipe:swarming/deterministic_build',
     execution_timeout = 6 * time.hour,
 )
 
 ci.android_builder(
     name = 'Deterministic Android (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'builder|det',
+        short_name = 'dbg',
+    ),
     executable = 'recipe:swarming/deterministic_build',
     execution_timeout = 6 * time.hour,
 )
 
 ci.android_builder(
     name = 'KitKat Phone Tester (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|phone',
+        short_name = 'K',
+    ),
     triggered_by = ['ci/Android arm Builder (dbg)'],
 )
 
 ci.android_builder(
     name = 'KitKat Tablet Tester',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|tablet',
+        short_name = 'K',
+    ),
     # We have limited tablet capacity and thus limited ability to run
     # tests in parallel, hence the high timeout.
     execution_timeout = 20 * time.hour,
@@ -204,6 +278,10 @@
 
 ci.android_builder(
     name = 'Lollipop Phone Tester',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|phone',
+        short_name = 'L',
+    ),
     # We have limited phone capacity and thus limited ability to run
     # tests in parallel, hence the high timeout.
     execution_timeout = 6 * time.hour,
@@ -212,6 +290,10 @@
 
 ci.android_builder(
     name = 'Lollipop Tablet Tester',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|tablet',
+        short_name = 'L',
+    ),
     # We have limited tablet capacity and thus limited ability to run
     # tests in parallel, hence the high timeout.
     execution_timeout = 20 * time.hour,
@@ -220,6 +302,10 @@
 
 ci.android_builder(
     name = 'Marshmallow Tablet Tester',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|tablet',
+        short_name = 'M',
+    ),
     # We have limited tablet capacity and thus limited ability to run
     # tests in parallel, hence the high timeout.
     execution_timeout = 12 * time.hour,
@@ -228,28 +314,47 @@
 
 ci.android_builder(
     name = 'android-arm64-proguard-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'builder_tester|arm64',
+        short_name = 'M proguard',
+    ),
     goma_jobs = goma.jobs.MANY_JOBS_FOR_CI,
     execution_timeout = 6 * time.hour,
 )
 
 ci.android_builder(
     name = 'android-cronet-arm64-dbg',
+    console_view_entry = ci.console_view_entry(
+        category = 'cronet|arm64',
+        short_name = 'dbg',
+    ),
     notifies = ['cronet'],
 )
 
 ci.android_builder(
     name = 'android-cronet-arm64-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'cronet|arm64',
+        short_name = 'rel',
+    ),
     notifies = ['cronet'],
 )
 
 ci.android_builder(
     name = 'android-cronet-asan-arm-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'cronet|asan',
+    ),
     notifies = ['cronet'],
 )
 
 # Runs on a specific machine with an attached phone
 ci.android_builder(
     name = 'android-cronet-marshmallow-arm64-perf-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'cronet|test|perf',
+        short_name = 'm',
+    ),
     cores = None,
     cpu = None,
     executable = 'recipe:cronet',
@@ -259,34 +364,61 @@
 
 ci.android_builder(
     name = 'android-cronet-marshmallow-arm64-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'cronet|test',
+        short_name = 'm',
+    ),
     notifies = ['cronet'],
     triggered_by = ['android-cronet-arm64-rel'],
 )
 
 ci.android_builder(
     name = 'android-cronet-x86-dbg',
+    console_view_entry = ci.console_view_entry(
+        category = 'cronet|x86',
+        short_name = 'dbg',
+    ),
     notifies = ['cronet'],
 )
 
 ci.android_builder(
     name = 'android-cronet-x86-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'cronet|x86',
+        short_name = 'rel',
+    ),
     notifies = ['cronet'],
 )
 
 ci.android_builder(
     name = 'android-incremental-dbg',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|incremental',
+    ),
 )
 
 ci.android_builder(
     name = 'android-lollipop-arm-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'on_cq',
+        short_name = 'L',
+    ),
 )
 
 ci.android_builder(
     name = 'android-pie-x86-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'builder_tester|x86',
+        short_name = 'P',
+    ),
 )
 
 ci.android_builder(
     name = 'android-10-arm64-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'builder_tester|arm64',
+        short_name = '10',
+    ),
 )
 
 
@@ -422,18 +554,34 @@
 
 ci.chromiumos_builder(
     name = 'Linux ChromiumOS Full',
+    console_view_entry = ci.console_view_entry(
+        category = 'default',
+        short_name = 'ful',
+    ),
 )
 
 ci.chromiumos_builder(
     name = 'chromeos-amd64-generic-asan-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'simple|release|x64',
+        short_name = 'asn',
+    ),
 )
 
 ci.chromiumos_builder(
     name = 'chromeos-amd64-generic-cfi-thin-lto-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'simple|release|x64',
+        short_name = 'cfi',
+    ),
 )
 
 ci.chromiumos_builder(
     name = 'chromeos-arm-generic-dbg',
+    console_view_entry = ci.console_view_entry(
+        category = 'simple|debug',
+        short_name = 'arm',
+    ),
 )
 
 
@@ -608,10 +756,18 @@
 
 ci.dawn_builder(
     name = 'Dawn Linux x64 Builder',
+    console_view_entry = ci.console_view_entry(
+        category = 'ToT|Linux|Builder',
+        short_name = 'x64',
+    ),
 )
 
 ci.dawn_builder(
     name = 'Dawn Linux x64 Release (Intel HD 630)',
+    console_view_entry = ci.console_view_entry(
+        category = 'ToT|Linux|Intel',
+        short_name = 'x64',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = ['Dawn Linux x64 Builder'],
@@ -619,6 +775,10 @@
 
 ci.dawn_builder(
     name = 'Dawn Linux x64 Release (NVIDIA)',
+    console_view_entry = ci.console_view_entry(
+        category = 'ToT|Linux|Nvidia',
+        short_name = 'x64',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = ['Dawn Linux x64 Builder'],
@@ -626,6 +786,10 @@
 
 ci.dawn_builder(
     name = 'Dawn Mac x64 Builder',
+    console_view_entry = ci.console_view_entry(
+        category = 'ToT|Mac|Builder',
+        short_name = 'x64',
+    ),
     builderless = False,
     cores = None,
     os = os.MAC_ANY,
@@ -633,6 +797,10 @@
 
 ci.dawn_builder(
     name = 'Dawn Mac x64 Release (AMD)',
+    console_view_entry = ci.console_view_entry(
+        category = 'ToT|Mac|AMD',
+        short_name = 'x64',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = ['Dawn Mac x64 Builder'],
@@ -640,6 +808,10 @@
 
 ci.dawn_builder(
     name = 'Dawn Mac x64 Release (Intel)',
+    console_view_entry = ci.console_view_entry(
+        category = 'ToT|Mac|Intel',
+        short_name = 'x64',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = ['Dawn Mac x64 Builder'],
@@ -647,11 +819,19 @@
 
 ci.dawn_builder(
     name = 'Dawn Win10 x86 Builder',
+    console_view_entry = ci.console_view_entry(
+        category = 'ToT|Windows|Builder',
+        short_name = 'x86',
+    ),
     os = os.WINDOWS_ANY,
 )
 
 ci.dawn_builder(
     name = 'Dawn Win10 x64 Builder',
+    console_view_entry = ci.console_view_entry(
+        category = 'ToT|Windows|Builder',
+        short_name = 'x64',
+    ),
     os = os.WINDOWS_ANY,
 )
 
@@ -659,6 +839,10 @@
 # physical Win hardware in the Swarming pool, which is why they run on linux
 ci.dawn_builder(
     name = 'Dawn Win10 x86 Release (Intel HD 630)',
+    console_view_entry = ci.console_view_entry(
+        category = 'ToT|Windows|Intel',
+        short_name = 'x86',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = ['Dawn Win10 x86 Builder'],
@@ -666,6 +850,10 @@
 
 ci.dawn_builder(
     name = 'Dawn Win10 x64 Release (Intel HD 630)',
+    console_view_entry = ci.console_view_entry(
+        category = 'ToT|Windows|Intel',
+        short_name = 'x64',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = ['Dawn Win10 x64 Builder'],
@@ -673,6 +861,10 @@
 
 ci.dawn_builder(
     name = 'Dawn Win10 x86 Release (NVIDIA)',
+    console_view_entry = ci.console_view_entry(
+        category = 'ToT|Windows|Nvidia',
+        short_name = 'x86',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = ['Dawn Win10 x86 Builder'],
@@ -680,6 +872,10 @@
 
 ci.dawn_builder(
     name = 'Dawn Win10 x64 Release (NVIDIA)',
+    console_view_entry = ci.console_view_entry(
+        category = 'ToT|Windows|Nvidia',
+        short_name = 'x64',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = ['Dawn Win10 x64 Builder'],
@@ -1771,6 +1967,10 @@
 
 ci.memory_builder(
     name = 'Linux CFI',
+    console_view_entry = ci.console_view_entry(
+        category = 'cfi',
+        short_name = 'lnx',
+    ),
     cores = 32,
     # TODO(thakis): Remove once https://crbug.com/927738 is resolved.
     execution_timeout = 4 * time.hour,
@@ -1779,6 +1979,10 @@
 
 ci.memory_builder(
     name = 'Linux Chromium OS ASan LSan Builder',
+    console_view_entry = ci.console_view_entry(
+        category = 'cros|asan',
+        short_name = 'bld',
+    ),
     # TODO(crbug.com/1030593): Builds take more than 3 hours sometimes. Remove
     # once the builds are faster.
     execution_timeout = 6 * time.hour,
@@ -1786,31 +1990,55 @@
 
 ci.memory_builder(
     name = 'Linux Chromium OS ASan LSan Tests (1)',
+    console_view_entry = ci.console_view_entry(
+        category = 'cros|asan',
+        short_name = 'tst',
+    ),
     triggered_by = ['Linux Chromium OS ASan LSan Builder'],
 )
 
 ci.memory_builder(
     name = 'Linux ChromiumOS MSan Builder',
+    console_view_entry = ci.console_view_entry(
+        category = 'cros|msan',
+        short_name = 'bld',
+    ),
 )
 
 ci.memory_builder(
     name = 'Linux ChromiumOS MSan Tests',
+    console_view_entry = ci.console_view_entry(
+        category = 'cros|msan',
+        short_name = 'tst',
+    ),
     triggered_by = ['Linux ChromiumOS MSan Builder'],
 )
 
 ci.memory_builder(
     name = 'Linux MSan Builder',
+    console_view_entry = ci.console_view_entry(
+        category = 'linux|msan',
+        short_name = 'bld',
+    ),
     goma_jobs = goma.jobs.MANY_JOBS_FOR_CI,
 )
 
 ci.memory_builder(
     name = 'Linux MSan Tests',
+    console_view_entry = ci.console_view_entry(
+        category = 'linux|msan',
+        short_name = 'tst',
+    ),
     triggered_by = ['Linux MSan Builder'],
 )
 
 ci.memory_builder(
     name = 'Mac ASan 64 Builder',
     builderless = False,
+    console_view_entry = ci.console_view_entry(
+        category = 'mac',
+        short_name = 'bld',
+    ),
     goma_debug = True,  # TODO(hinoka): Remove this after debugging.
     goma_jobs = None,
     cores = None,  # Swapping between 8 and 24
@@ -1823,28 +2051,52 @@
 ci.memory_builder(
     name = 'Mac ASan 64 Tests (1)',
     builderless = False,
+    console_view_entry = ci.console_view_entry(
+        category = 'mac',
+        short_name = 'tst',
+    ),
     os = os.MAC_DEFAULT,
     triggered_by = ['Mac ASan 64 Builder'],
 )
 
 ci.memory_builder(
     name = 'WebKit Linux ASAN',
+    console_view_entry = ci.console_view_entry(
+        category = 'linux|webkit',
+        short_name = 'asn',
+    ),
 )
 
 ci.memory_builder(
     name = 'WebKit Linux Leak',
+    console_view_entry = ci.console_view_entry(
+        category = 'linux|webkit',
+        short_name = 'lk',
+    ),
 )
 
 ci.memory_builder(
     name = 'WebKit Linux MSAN',
+    console_view_entry = ci.console_view_entry(
+        category = 'linux|webkit',
+        short_name = 'msn',
+    ),
 )
 
 ci.memory_builder(
     name = 'android-asan',
+    console_view_entry = ci.console_view_entry(
+        category = 'android',
+        short_name = 'asn',
+    ),
 )
 
 ci.memory_builder(
     name = 'win-asan',
+    console_view_entry = ci.console_view_entry(
+        category = 'win',
+        short_name = 'asn',
+    ),
     cores = 32,
     builderless = True,
     os = os.WINDOWS_DEFAULT,
diff --git a/infra/config/consoles/chromium.android.star b/infra/config/consoles/chromium.android.star
deleted file mode 100644
index 76e9129..0000000
--- a/infra/config/consoles/chromium.android.star
+++ /dev/null
@@ -1,210 +0,0 @@
-luci.console_view(
-    name = 'chromium.android',
-    header = '//consoles/chromium-header.textpb',
-    repo = 'https://chromium.googlesource.com/chromium/src',
-    entries = [
-        luci.console_view_entry(
-            builder = 'ci/android-cronet-arm-dbg',
-            category = 'cronet|arm',
-            short_name = 'dbg',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-cronet-arm-rel',
-            category = 'cronet|arm',
-            short_name = 'rel',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-cronet-arm64-dbg',
-            category = 'cronet|arm64',
-            short_name = 'dbg',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-cronet-arm64-rel',
-            category = 'cronet|arm64',
-            short_name = 'rel',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-cronet-x86-dbg',
-            category = 'cronet|x86',
-            short_name = 'dbg',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-cronet-x86-rel',
-            category = 'cronet|x86',
-            short_name = 'rel',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-cronet-asan-arm-rel',
-            category = 'cronet|asan',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-cronet-kitkat-arm-rel',
-            category = 'cronet|test',
-            short_name = 'k',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-cronet-lollipop-arm-rel',
-            category = 'cronet|test',
-            short_name = 'l',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-cronet-marshmallow-arm64-rel',
-            category = 'cronet|test',
-            short_name = 'm',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-cronet-marshmallow-arm64-perf-rel',
-            category = 'cronet|test|perf',
-            short_name = 'm',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Android arm Builder (dbg)',
-            category = 'builder|arm',
-            short_name = '32',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Android arm64 Builder (dbg)',
-            category = 'builder|arm',
-            short_name = '64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Android x86 Builder (dbg)',
-            category = 'builder|x86',
-            short_name = '32',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Android x64 Builder (dbg)',
-            category = 'builder|x86',
-            short_name = '64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Deterministic Android',
-            category = 'builder|det',
-            short_name = 'rel',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Deterministic Android (dbg)',
-            category = 'builder|det',
-            short_name = 'dbg',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/KitKat Phone Tester (dbg)',
-            category = 'tester|phone',
-            short_name = 'K',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Lollipop Phone Tester',
-            category = 'tester|phone',
-            short_name = 'L',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-lollipop-arm-rel',
-            category = 'tester|phone|rel',
-            short_name = 'L',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Marshmallow 64 bit Tester',
-            category = 'tester|phone',
-            short_name = 'M',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Nougat Phone Tester',
-            category = 'tester|phone',
-            short_name = 'N',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Oreo Phone Tester',
-            category = 'tester|phone',
-            short_name = 'O',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-pie-arm64-dbg',
-            category = 'tester|phone',
-            short_name = 'P',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/KitKat Tablet Tester',
-            category = 'tester|tablet',
-            short_name = 'K',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Lollipop Tablet Tester',
-            category = 'tester|tablet',
-            short_name = 'L',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Marshmallow Tablet Tester',
-            category = 'tester|tablet',
-            short_name = 'M',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-incremental-dbg',
-            category = 'tester|incremental',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Android WebView L (dbg)',
-            category = 'tester|webview',
-            short_name = 'L',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Android WebView M (dbg)',
-            category = 'tester|webview',
-            short_name = 'M',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Android WebView N (dbg)',
-            category = 'tester|webview',
-            short_name = 'N',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Android WebView O (dbg)',
-            category = 'tester|webview',
-            short_name = 'O',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Android WebView P (dbg)',
-            category = 'tester|webview',
-            short_name = 'P',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-arm64-proguard-rel',
-            category = 'builder_tester|arm64',
-            short_name = 'M proguard',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-10-arm64-rel',
-            category = 'builder_tester|arm64',
-            short_name = '10',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-pie-x86-rel',
-            category = 'builder_tester|x86',
-            short_name = 'P',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-kitkat-arm-rel',
-            category = 'on_cq',
-            short_name = 'K',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-marshmallow-arm64-rel',
-            category = 'on_cq',
-            short_name = 'M',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Cast Android (dbg)',
-            category = 'on_cq',
-            short_name = 'cst',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Android ASAN (dbg)',
-            category = 'on_cq',
-            short_name = 'san',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-pie-arm64-rel',
-            category = 'on_cq',
-            short_name = 'P',
-        ),
-    ],
-)
diff --git a/infra/config/consoles/chromium.chromiumos.star b/infra/config/consoles/chromium.chromiumos.star
deleted file mode 100644
index b24964f1..0000000
--- a/infra/config/consoles/chromium.chromiumos.star
+++ /dev/null
@@ -1,57 +0,0 @@
-luci.console_view(
-    name = 'chromium.chromiumos',
-    header = '//consoles/chromium-header.textpb',
-    repo = 'https://chromium.googlesource.com/chromium/src',
-    entries = [
-        luci.console_view_entry(
-            builder = 'ci/Linux ChromiumOS Full',
-            category = 'default',
-            short_name = 'ful',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/linux-chromeos-rel',
-            category = 'default',
-            short_name = 'rel',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/linux-chromeos-dbg',
-            category = 'default',
-            short_name = 'dbg',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/chromeos-amd64-generic-asan-rel',
-            category = 'simple|release|x64',
-            short_name = 'asn',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/chromeos-amd64-generic-cfi-thin-lto-rel',
-            category = 'simple|release|x64',
-            short_name = 'cfi',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/chromeos-amd64-generic-dbg',
-            category = 'simple|debug|x64',
-            short_name = 'dbg',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/chromeos-amd64-generic-rel',
-            category = 'simple|release|x64',
-            short_name = 'rel',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/chromeos-arm-generic-dbg',
-            category = 'simple|debug',
-            short_name = 'arm',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/chromeos-arm-generic-rel',
-            category = 'simple|release',
-            short_name = 'arm',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/chromeos-kevin-rel',
-            category = 'simple|release',
-            short_name = 'kvn',
-        ),
-    ],
-)
diff --git a/infra/config/consoles/chromium.dawn.star b/infra/config/consoles/chromium.dawn.star
deleted file mode 100644
index d8c95eb..0000000
--- a/infra/config/consoles/chromium.dawn.star
+++ /dev/null
@@ -1,127 +0,0 @@
-luci.console_view(
-    name = 'chromium.dawn',
-    header = '//consoles/chromium-header.textpb',
-    repo = 'https://chromium.googlesource.com/chromium/src',
-    entries = [
-        luci.console_view_entry(
-            builder = 'ci/Dawn Linux x64 Builder',
-            category = 'ToT|Linux|Builder',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Linux x64 Release (Intel HD 630)',
-            category = 'ToT|Linux|Intel',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Linux x64 Release (NVIDIA)',
-            category = 'ToT|Linux|Nvidia',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Mac x64 Builder',
-            category = 'ToT|Mac|Builder',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Mac x64 Release (AMD)',
-            category = 'ToT|Mac|AMD',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Mac x64 Release (Intel)',
-            category = 'ToT|Mac|Intel',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Win10 x86 Builder',
-            category = 'ToT|Windows|Builder',
-            short_name = 'x86',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Win10 x64 Builder',
-            category = 'ToT|Windows|Builder',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Win10 x86 Release (Intel HD 630)',
-            category = 'ToT|Windows|Intel',
-            short_name = 'x86',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Win10 x64 Release (Intel HD 630)',
-            category = 'ToT|Windows|Intel',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Win10 x86 Release (NVIDIA)',
-            category = 'ToT|Windows|Nvidia',
-            short_name = 'x86',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Win10 x64 Release (NVIDIA)',
-            category = 'ToT|Windows|Nvidia',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Linux x64 DEPS Builder',
-            category = 'DEPS|Linux|Builder',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Linux x64 DEPS Release (Intel HD 630)',
-            category = 'DEPS|Linux|Intel',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Linux x64 DEPS Release (NVIDIA)',
-            category = 'DEPS|Linux|Nvidia',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Mac x64 DEPS Builder',
-            category = 'DEPS|Mac|Builder',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Mac x64 DEPS Release (AMD)',
-            category = 'DEPS|Mac|AMD',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Mac x64 DEPS Release (Intel)',
-            category = 'DEPS|Mac|Intel',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Win10 x86 DEPS Builder',
-            category = 'DEPS|Windows|Builder',
-            short_name = 'x86',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Win10 x64 DEPS Builder',
-            category = 'DEPS|Windows|Builder',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Win10 x86 DEPS Release (Intel HD 630)',
-            category = 'DEPS|Windows|Intel',
-            short_name = 'x86',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Win10 x64 DEPS Release (Intel HD 630)',
-            category = 'DEPS|Windows|Intel',
-            short_name = 'x64',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Win10 x86 DEPS Release (NVIDIA)',
-            category = 'DEPS|Windows|Nvidia',
-            short_name = 'x86',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Dawn Win10 x64 DEPS Release (NVIDIA)',
-            category = 'DEPS|Windows|Nvidia',
-            short_name = 'x64',
-        ),
-    ],
-)
diff --git a/infra/config/consoles/chromium.memory.star b/infra/config/consoles/chromium.memory.star
deleted file mode 100644
index 5e2889a..0000000
--- a/infra/config/consoles/chromium.memory.star
+++ /dev/null
@@ -1,102 +0,0 @@
-luci.console_view(
-    name = 'chromium.memory',
-    header = '//consoles/chromium-header.textpb',
-    repo = 'https://chromium.googlesource.com/chromium/src',
-    entries = [
-        luci.console_view_entry(
-            builder = 'ci/win-asan',
-            category = 'win',
-            short_name = 'asn',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Mac ASan 64 Builder',
-            category = 'mac',
-            short_name = 'bld',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Mac ASan 64 Tests (1)',
-            category = 'mac',
-            short_name = 'tst',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Linux TSan Builder',
-            category = 'linux|TSan v2',
-            short_name = 'bld',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Linux TSan Tests',
-            category = 'linux|TSan v2',
-            short_name = 'tst',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Linux ASan LSan Builder',
-            category = 'linux|asan lsan',
-            short_name = 'bld',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Linux ASan LSan Tests (1)',
-            category = 'linux|asan lsan',
-            short_name = 'tst',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Linux ASan Tests (sandboxed)',
-            category = 'linux|asan lsan',
-            short_name = 'sbx',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Linux MSan Builder',
-            category = 'linux|msan',
-            short_name = 'bld',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Linux MSan Tests',
-            category = 'linux|msan',
-            short_name = 'tst',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/WebKit Linux ASAN',
-            category = 'linux|webkit',
-            short_name = 'asn',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/WebKit Linux MSAN',
-            category = 'linux|webkit',
-            short_name = 'msn',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/WebKit Linux Leak',
-            category = 'linux|webkit',
-            short_name = 'lk',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Linux Chromium OS ASan LSan Builder',
-            category = 'cros|asan',
-            short_name = 'bld',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Linux Chromium OS ASan LSan Tests (1)',
-            category = 'cros|asan',
-            short_name = 'tst',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Linux ChromiumOS MSan Builder',
-            category = 'cros|msan',
-            short_name = 'bld',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Linux ChromiumOS MSan Tests',
-            category = 'cros|msan',
-            short_name = 'tst',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/android-asan',
-            category = 'android',
-            short_name = 'asn',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Linux CFI',
-            category = 'cfi',
-            short_name = 'lnx',
-        ),
-    ],
-)
diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg
index d3634ca..084f8de 100644
--- a/infra/config/generated/luci-milo.cfg
+++ b/infra/config/generated/luci-milo.cfg
@@ -758,11 +758,6 @@
     short_name: "L"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/android-lollipop-arm-rel"
-    category: "tester|phone|rel"
-    short_name: "L"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Marshmallow 64 bit Tester"
     category: "tester|phone"
     short_name: "M"
@@ -847,11 +842,21 @@
     short_name: "K"
   >
   builders: <
+    name: "buildbucket/luci.chromium.ci/android-lollipop-arm-rel"
+    category: "on_cq"
+    short_name: "L"
+  >
+  builders: <
     name: "buildbucket/luci.chromium.ci/android-marshmallow-arm64-rel"
     category: "on_cq"
     short_name: "M"
   >
   builders: <
+    name: "buildbucket/luci.chromium.ci/android-pie-arm64-rel"
+    category: "on_cq"
+    short_name: "P"
+  >
+  builders: <
     name: "buildbucket/luci.chromium.ci/Cast Android (dbg)"
     category: "on_cq"
     short_name: "cst"
@@ -861,11 +866,6 @@
     category: "on_cq"
     short_name: "san"
   >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/android-pie-arm64-rel"
-    category: "on_cq"
-    short_name: "P"
-  >
   header: <
     oncalls: <
       name: "Chromium"
@@ -1460,6 +1460,16 @@
     short_name: "dbg"
   >
   builders: <
+    name: "buildbucket/luci.chromium.ci/chromeos-arm-generic-rel"
+    category: "simple|release"
+    short_name: "arm"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci/chromeos-kevin-rel"
+    category: "simple|release"
+    short_name: "kvn"
+  >
+  builders: <
     name: "buildbucket/luci.chromium.ci/chromeos-amd64-generic-asan-rel"
     category: "simple|release|x64"
     short_name: "asn"
@@ -1470,11 +1480,6 @@
     short_name: "cfi"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/chromeos-amd64-generic-dbg"
-    category: "simple|debug|x64"
-    short_name: "dbg"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/chromeos-amd64-generic-rel"
     category: "simple|release|x64"
     short_name: "rel"
@@ -1485,14 +1490,9 @@
     short_name: "arm"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/chromeos-arm-generic-rel"
-    category: "simple|release"
-    short_name: "arm"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/chromeos-kevin-rel"
-    category: "simple|release"
-    short_name: "kvn"
+    name: "buildbucket/luci.chromium.ci/chromeos-amd64-generic-dbg"
+    category: "simple|debug|x64"
+    short_name: "dbg"
   >
   header: <
     oncalls: <
@@ -10733,16 +10733,6 @@
     short_name: "dbg"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/chromeos-amd64-generic-dbg"
-    category: "chromium.chromiumos|simple|debug|x64"
-    short_name: "dbg"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/chromeos-amd64-generic-rel"
-    category: "chromium.chromiumos|simple|release|x64"
-    short_name: "rel"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/chromeos-arm-generic-rel"
     category: "chromium.chromiumos|simple|release"
     short_name: "arm"
@@ -10753,6 +10743,16 @@
     short_name: "kvn"
   >
   builders: <
+    name: "buildbucket/luci.chromium.ci/chromeos-amd64-generic-rel"
+    category: "chromium.chromiumos|simple|release|x64"
+    short_name: "rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci/chromeos-amd64-generic-dbg"
+    category: "chromium.chromiumos|simple|debug|x64"
+    short_name: "dbg"
+  >
+  builders: <
     name: "buildbucket/luci.chromium.ci/android-cronet-arm-dbg"
     category: "chromium.android|cronet|arm"
     short_name: "dbg"
@@ -10873,16 +10873,16 @@
     short_name: "M"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Cast Android (dbg)"
-    category: "chromium.android|on_cq"
-    short_name: "cst"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/android-pie-arm64-rel"
     category: "chromium.android|on_cq"
     short_name: "P"
   >
   builders: <
+    name: "buildbucket/luci.chromium.ci/Cast Android (dbg)"
+    category: "chromium.android|on_cq"
+    short_name: "cst"
+  >
+  builders: <
     name: "buildbucket/luci.chrome.ci/linux-chromeos-chrome"
     category: "chrome"
     short_name: "cro"
diff --git a/infra/config/lib/ci.star b/infra/config/lib/ci.star
index 5f5c980..2d2a317e 100644
--- a/infra/config/lib/ci.star
+++ b/infra/config/lib/ci.star
@@ -84,6 +84,8 @@
   if builder.category:
     for c in builder.category.split('|'):
       ordering = console_ordering.get(category, [])
+      if type(ordering) == type(''):
+        ordering = console_ordering[ordering]
       if type(ordering) == type(struct()):
         ordering = ordering.categories
       category_key.append(_level_sort_key(c, ordering))
@@ -92,6 +94,8 @@
   short_name_key = ()
   if builder.short_name:
     ordering = console_ordering.get(category, [])
+    if type(ordering) == type(''):
+      ordering = console_ordering[ordering]
     short_name_ordering = getattr(ordering, 'short_names', [])
     short_name_key = _level_sort_key(builder.short_name, short_name_ordering)
 
@@ -154,7 +158,8 @@
           the short names of builders that have no category.
       2.  str: Category string to apply the ordering to the next nested
           level of categories and/or the short names of builders with
-          that category.
+          that category. Arbitrary strings can be used also, which can
+          be used as aliases for other entries to refer to.
 
       The value for each entry defines the ordering to be applied to
       builders that have matched the sequence of category components
@@ -163,6 +168,9 @@
           details.
       2.  list of category components: Equivalent to a `ci.ordering`
           call that only specifies `categories` with the given list.
+      3.  str: An alias for another category. The string must be another
+          key in the dict. The ordering will be looked up by that key
+          instead.
     kwargs - Additional keyword arguments to forward on to
       `luci.console_view`. The header and repo arguments support
        module-level defaults.
diff --git a/infra/config/main.star b/infra/config/main.star
index 6e10b2a..2bc802b 100755
--- a/infra/config/main.star
+++ b/infra/config/main.star
@@ -104,11 +104,8 @@
 
 exec('//consoles/android.packager.star')
 exec('//consoles/angle.try.star')
-exec('//consoles/chromium.android.star')
 exec('//consoles/chromium.android.fyi.star')
-exec('//consoles/chromium.chromiumos.star')
 exec('//consoles/chromium.clang.star')
-exec('//consoles/chromium.dawn.star')
 exec('//consoles/chromium.fuzz.star')
 exec('//consoles/chromium.fyi.star')
 exec('//consoles/chromium.goma.star')
@@ -116,7 +113,6 @@
 exec('//consoles/chromium.goma.migration.star')
 exec('//consoles/chromium.gpu.star')
 exec('//consoles/chromium.gpu.fyi.star')
-exec('//consoles/chromium.memory.star')
 exec('//consoles/chromium.swangle.star')
 exec('//consoles/chromium.webrtc.star')
 exec('//consoles/chromium.webrtc.fyi.star')
diff --git a/infra/config/versioned/trunk/buckets/ci.star b/infra/config/versioned/trunk/buckets/ci.star
index 92696186..3722856c 100644
--- a/infra/config/versioned/trunk/buckets/ci.star
+++ b/infra/config/versioned/trunk/buckets/ci.star
@@ -42,117 +42,213 @@
 
 ci.android_builder(
     name = 'Android WebView M (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|webview',
+        short_name = 'M',
+    ),
     triggered_by = [builder_name('Android arm64 Builder (dbg)')],
 )
 
 ci.android_builder(
     name = 'Android WebView N (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|webview',
+        short_name = 'N',
+    ),
     triggered_by = [builder_name('Android arm64 Builder (dbg)')],
 )
 
 ci.android_builder(
     name = 'Android WebView O (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|webview',
+        short_name = 'O',
+    ),
     triggered_by = [builder_name('Android arm64 Builder (dbg)')],
 )
 
 ci.android_builder(
     name = 'Android WebView P (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|webview',
+        short_name = 'P',
+    ),
     triggered_by = [builder_name('Android arm64 Builder (dbg)')],
 )
 
 ci.android_builder(
     name = 'Android arm Builder (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'builder|arm',
+        short_name = '32',
+    ),
     execution_timeout = 4 * time.hour,
 )
 
 ci.android_builder(
     name = 'Android arm64 Builder (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'builder|arm',
+        short_name = '64',
+    ),
     goma_jobs = goma.jobs.MANY_JOBS_FOR_CI,
     execution_timeout = 4 * time.hour,
 )
 
 ci.android_builder(
     name = 'Android x64 Builder (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'builder|x86',
+        short_name = '64',
+    ),
     execution_timeout = 4 * time.hour,
 )
 
 ci.android_builder(
     name = 'Android x86 Builder (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'builder|x86',
+        short_name = '32',
+    ),
 )
 
 ci.android_builder(
     name = 'Cast Android (dbg)',
+    console_view_entry = ci.console_view_entry(
+        category = 'on_cq',
+        short_name = 'cst',
+    ),
 )
 
 ci.android_builder(
     name = 'Marshmallow 64 bit Tester',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|phone',
+        short_name = 'M',
+    ),
     triggered_by = [builder_name('Android arm64 Builder (dbg)')],
 )
 
 ci.android_builder(
     name = 'Nougat Phone Tester',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|phone',
+        short_name = 'N',
+    ),
     triggered_by = [builder_name('Android arm64 Builder (dbg)')],
 )
 
 ci.android_builder(
     name = 'Oreo Phone Tester',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|phone',
+        short_name = 'O',
+    ),
     triggered_by = [builder_name('Android arm64 Builder (dbg)')],
 )
 
 ci.android_builder(
     name = 'android-cronet-arm-dbg',
+    console_view_entry = ci.console_view_entry(
+        category = 'cronet|arm',
+        short_name = 'dbg',
+    ),
     notifies = ['cronet'],
 )
 
 ci.android_builder(
     name = 'android-cronet-arm-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'cronet|arm',
+        short_name = 'rel',
+    ),
     notifies = ['cronet'],
 )
 
 ci.android_builder(
     name = 'android-cronet-kitkat-arm-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'cronet|test',
+        short_name = 'k',
+    ),
     notifies = ['cronet'],
     triggered_by = [builder_name('android-cronet-arm-rel')],
 )
 
 ci.android_builder(
     name = 'android-cronet-lollipop-arm-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'cronet|test',
+        short_name = 'l',
+    ),
     notifies = ['cronet'],
     triggered_by = [builder_name('android-cronet-arm-rel')],
 )
 
 ci.android_builder(
     name = 'android-kitkat-arm-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'on_cq',
+        short_name = 'K',
+    ),
 )
 
 ci.android_builder(
     name = 'android-marshmallow-arm64-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'on_cq',
+        short_name = 'M',
+    ),
 )
 
 ci.android_builder(
     name = 'android-pie-arm64-dbg',
+    console_view_entry = ci.console_view_entry(
+        category = 'tester|phone',
+        short_name = 'P',
+    ),
     triggered_by = [builder_name('Android arm64 Builder (dbg)')],
 )
 
 ci.android_builder(
     name = 'android-pie-arm64-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'on_cq',
+        short_name = 'P',
+    ),
 )
 
 
 ci.chromiumos_builder(
     name = 'chromeos-amd64-generic-dbg',
+    console_view_entry = ci.console_view_entry(
+        category = 'simple|debug|x64',
+        short_name = 'dbg',
+    ),
 )
 
 ci.chromiumos_builder(
     name = 'chromeos-amd64-generic-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'simple|release|x64',
+        short_name = 'rel',
+    ),
 )
 
 ci.chromiumos_builder(
     name = 'chromeos-arm-generic-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'simple|release',
+        short_name = 'arm',
+    ),
 )
 
 ci.chromiumos_builder(
     name = 'chromeos-kevin-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'simple|release',
+        short_name = 'kvn',
+    ),
 )
 
 ci.fyi_builder(
@@ -161,19 +257,35 @@
 
 ci.chromiumos_builder(
     name = 'linux-chromeos-dbg',
+    console_view_entry = ci.console_view_entry(
+        category = 'default',
+        short_name = 'dbg',
+    ),
 )
 
 ci.chromiumos_builder(
     name = 'linux-chromeos-rel',
+    console_view_entry = ci.console_view_entry(
+        category = 'default',
+        short_name = 'rel',
+    ),
 )
 
 
 ci.dawn_builder(
     name = 'Dawn Linux x64 DEPS Builder',
+    console_view_entry = ci.console_view_entry(
+        category = 'DEPS|Linux|Builder',
+        short_name = 'x64',
+    ),
 )
 
 ci.dawn_builder(
     name = 'Dawn Linux x64 DEPS Release (Intel HD 630)',
+    console_view_entry = ci.console_view_entry(
+        category = 'DEPS|Linux|Intel',
+        short_name = 'x64',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = [builder_name('Dawn Linux x64 DEPS Builder')],
@@ -181,6 +293,10 @@
 
 ci.dawn_builder(
     name = 'Dawn Linux x64 DEPS Release (NVIDIA)',
+    console_view_entry = ci.console_view_entry(
+        category = 'DEPS|Linux|Nvidia',
+        short_name = 'x64',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = [builder_name('Dawn Linux x64 DEPS Builder')],
@@ -189,6 +305,10 @@
 ci.dawn_builder(
     name = 'Dawn Mac x64 DEPS Builder',
     builderless = False,
+    console_view_entry = ci.console_view_entry(
+        category = 'DEPS|Mac|Builder',
+        short_name = 'x64',
+    ),
     cores = None,
     os = os.MAC_ANY,
 )
@@ -197,6 +317,10 @@
 # physical Mac hardware in the Swarming pool which is why they run on linux
 ci.dawn_builder(
     name = 'Dawn Mac x64 DEPS Release (AMD)',
+    console_view_entry = ci.console_view_entry(
+        category = 'DEPS|Mac|AMD',
+        short_name = 'x64',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = [builder_name('Dawn Mac x64 DEPS Builder')],
@@ -204,6 +328,10 @@
 
 ci.dawn_builder(
     name = 'Dawn Mac x64 DEPS Release (Intel)',
+    console_view_entry = ci.console_view_entry(
+        category = 'DEPS|Mac|Intel',
+        short_name = 'x64',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = [builder_name('Dawn Mac x64 DEPS Builder')],
@@ -211,11 +339,19 @@
 
 ci.dawn_builder(
     name = 'Dawn Win10 x64 DEPS Builder',
+    console_view_entry = ci.console_view_entry(
+        category = 'DEPS|Windows|Builder',
+        short_name = 'x64',
+    ),
     os = os.WINDOWS_ANY,
 )
 
 ci.dawn_builder(
     name = 'Dawn Win10 x64 DEPS Release (Intel HD 630)',
+    console_view_entry = ci.console_view_entry(
+        category = 'DEPS|Windows|Intel',
+        short_name = 'x64',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = [builder_name('Dawn Win10 x64 DEPS Builder')],
@@ -223,6 +359,10 @@
 
 ci.dawn_builder(
     name = 'Dawn Win10 x64 DEPS Release (NVIDIA)',
+    console_view_entry = ci.console_view_entry(
+        category = 'DEPS|Windows|Nvidia',
+        short_name = 'x64',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = [builder_name('Dawn Win10 x64 DEPS Builder')],
@@ -230,11 +370,19 @@
 
 ci.dawn_builder(
     name = 'Dawn Win10 x86 DEPS Builder',
+    console_view_entry = ci.console_view_entry(
+        category = 'DEPS|Windows|Builder',
+        short_name = 'x86',
+    ),
     os = os.WINDOWS_ANY,
 )
 
 ci.dawn_builder(
     name = 'Dawn Win10 x86 DEPS Release (Intel HD 630)',
+    console_view_entry = ci.console_view_entry(
+        category = 'DEPS|Windows|Intel',
+        short_name = 'x86',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = [builder_name('Dawn Win10 x86 DEPS Builder')],
@@ -242,6 +390,10 @@
 
 ci.dawn_builder(
     name = 'Dawn Win10 x86 DEPS Release (NVIDIA)',
+    console_view_entry = ci.console_view_entry(
+        category = 'DEPS|Windows|Nvidia',
+        short_name = 'x86',
+    ),
     cores = 2,
     os = os.LINUX_DEFAULT,
     triggered_by = [builder_name('Dawn Win10 x86 DEPS Builder')],
@@ -537,25 +689,45 @@
 
 ci.memory_builder(
     name = 'Linux ASan LSan Builder',
+    console_view_entry = ci.console_view_entry(
+        category = 'linux|asan lsan',
+        short_name = 'bld',
+    ),
     ssd = True,
 )
 
 ci.memory_builder(
     name = 'Linux ASan LSan Tests (1)',
+    console_view_entry = ci.console_view_entry(
+        category = 'linux|asan lsan',
+        short_name = 'tst',
+    ),
     triggered_by = [builder_name('Linux ASan LSan Builder')],
 )
 
 ci.memory_builder(
     name = 'Linux ASan Tests (sandboxed)',
+    console_view_entry = ci.console_view_entry(
+        category = 'linux|asan lsan',
+        short_name = 'sbx',
+    ),
     triggered_by = [builder_name('Linux ASan LSan Builder')],
 )
 
 ci.memory_builder(
     name = 'Linux TSan Builder',
+    console_view_entry = ci.console_view_entry(
+        category = 'linux|TSan v2',
+        short_name = 'bld',
+    ),
 )
 
 ci.memory_builder(
     name = 'Linux TSan Tests',
+    console_view_entry = ci.console_view_entry(
+        category = 'linux|TSan v2',
+        short_name = 'tst',
+    ),
     triggered_by = [builder_name('Linux TSan Builder')],
 )
 
diff --git a/infra/config/versioned/trunk/consoles/main.star b/infra/config/versioned/trunk/consoles/main.star
index 4e5aee8..7ec7bcd 100644
--- a/infra/config/versioned/trunk/consoles/main.star
+++ b/infra/config/versioned/trunk/consoles/main.star
@@ -200,16 +200,6 @@
             short_name = 'dbg',
         ),
         luci.console_view_entry(
-            builder = builder_name('chromeos-amd64-generic-dbg'),
-            category = 'chromium.chromiumos|simple|debug|x64',
-            short_name = 'dbg',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('chromeos-amd64-generic-rel'),
-            category = 'chromium.chromiumos|simple|release|x64',
-            short_name = 'rel',
-        ),
-        luci.console_view_entry(
             builder = builder_name('chromeos-arm-generic-rel'),
             category = 'chromium.chromiumos|simple|release',
             short_name = 'arm',
@@ -220,6 +210,16 @@
             short_name = 'kvn',
         ),
         luci.console_view_entry(
+            builder = builder_name('chromeos-amd64-generic-rel'),
+            category = 'chromium.chromiumos|simple|release|x64',
+            short_name = 'rel',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('chromeos-amd64-generic-dbg'),
+            category = 'chromium.chromiumos|simple|debug|x64',
+            short_name = 'dbg',
+        ),
+        luci.console_view_entry(
             builder = builder_name('android-cronet-arm-dbg'),
             category = 'chromium.android|cronet|arm',
             short_name = 'dbg',
@@ -340,16 +340,16 @@
             short_name = 'M',
         ),
         luci.console_view_entry(
-            builder = builder_name('Cast Android (dbg)'),
-            category = 'chromium.android|on_cq',
-            short_name = 'cst',
-        ),
-        luci.console_view_entry(
             builder = builder_name('android-pie-arm64-rel'),
             category = 'chromium.android|on_cq',
             short_name = 'P',
         ),
         luci.console_view_entry(
+            builder = builder_name('Cast Android (dbg)'),
+            category = 'chromium.android|on_cq',
+            short_name = 'cst',
+        ),
+        luci.console_view_entry(
             builder = 'chrome:ci/linux-chromeos-chrome',
             category = 'chrome',
             short_name = 'cro',
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 058eda4..3005e30a6 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -196,10 +196,10 @@
         You can always choose what to sync in <ph name="BEGIN_LINK">BEGIN_LINK</ph>settings<ph name="END_LINK">END_LINK</ph>.
       </message>
       <message name="IDS_IOS_ACCOUNT_UNIFIED_CONSENT_ADD_ACCOUNT" desc="Title of the button to add an account [Length: 15m] [iOS only]">
-        ADD ACCOUNT
+        Add Account
       </message>
       <message name="IDS_IOS_ACCOUNT_UNIFIED_CONSENT_OK_BUTTON" desc="Title of the button to validate the user consent [Length: 15m] [iOS only]">
-        YES, I'M IN
+        Yes, I'm In
       </message>
       <message name="IDS_IOS_ADD_CREDIT_CARD_INVALID_CARD_NUMBER_ALERT" desc="The title of the alert view displaying invalid card number when a user enters invalid card number for the new credit card to save. [iOS only]">
           Invalid Card Number
@@ -771,7 +771,7 @@
         Call
       </message>
       <message name="IDS_IOS_FIRSTRUN_ACCOUNT_CONSISTENCY_SKIP_BUTTON" desc="Title of the button to skip the selection of an account on First Run when account consistency is enabled. [Length: 10em] [iOS only]">
-        No thanks
+        No Thanks
       </message>
       <message name="IDS_IOS_FIRSTRUN_OPT_IN_ACCEPT_BUTTON" desc="User metrics opt-in acceptance button label [iOS only]">
         Accept &amp; Continue
diff --git a/ios/chrome/browser/chrome_switches.cc b/ios/chrome/browser/chrome_switches.cc
index 19f152fd..1def9622 100644
--- a/ios/chrome/browser/chrome_switches.cc
+++ b/ios/chrome/browser/chrome_switches.cc
@@ -36,6 +36,10 @@
 const char kEnableThirdPartyKeyboardWorkaround[] =
     "enable-third-party-keyboard-workaround";
 
+// Installs the managed bookmarks policy handler.
+const char kInstallManagedBookmarksHandler[] =
+    "install-managed-bookmarks-handler";
+
 // A string used to override the default user agent with a custom one.
 const char kUserAgent[] = "user-agent";
 
diff --git a/ios/chrome/browser/chrome_switches.h b/ios/chrome/browser/chrome_switches.h
index 7e82fc444..5f23b2b 100644
--- a/ios/chrome/browser/chrome_switches.h
+++ b/ios/chrome/browser/chrome_switches.h
@@ -16,6 +16,7 @@
 extern const char kEnableIOSHandoffToOtherDevices[];
 extern const char kEnableSpotlightActions[];
 extern const char kEnableThirdPartyKeyboardWorkaround[];
+extern const char kInstallManagedBookmarksHandler[];
 
 extern const char kUserAgent[];
 
diff --git a/ios/chrome/browser/flags/BUILD.gn b/ios/chrome/browser/flags/BUILD.gn
index 9ce2e64..590df86 100644
--- a/ios/chrome/browser/flags/BUILD.gn
+++ b/ios/chrome/browser/flags/BUILD.gn
@@ -46,6 +46,7 @@
     "//ios/chrome/browser/drag_and_drop",
     "//ios/chrome/browser/find_in_page:feature_flags",
     "//ios/chrome/browser/passwords:feature_flags",
+    "//ios/chrome/browser/policy:feature_flags",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/download:features",
     "//ios/chrome/browser/ui/fullscreen:feature_flags",
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index cc90d7d..7895ae6 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -57,6 +57,7 @@
 #include "ios/chrome/browser/find_in_page/features.h"
 #include "ios/chrome/browser/flags/ios_chrome_flag_descriptions.h"
 #include "ios/chrome/browser/passwords/password_manager_features.h"
+#include "ios/chrome/browser/policy/policy_features.h"
 #include "ios/chrome/browser/system_flags.h"
 #import "ios/chrome/browser/ui/download/features.h"
 #import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h"
@@ -592,6 +593,9 @@
      flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(
          autofill::features::kAutofillEnableSurfacingServerCardNickname)},
+    {"managed-bookmarks-ios", flag_descriptions::kManagedBookmarksIOSName,
+     flag_descriptions::kManagedBookmarksIOSDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(kManagedBookmarksIOS)},
 };
 
 // Add all switches from experimental flags to |command_line|.
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index dbe61df..86307e3 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -278,6 +278,11 @@
     "When enabled, the bottom toolbar will not get collapsed when scrolling "
     "into fullscreen mode.";
 
+const char kManagedBookmarksIOSName[] = "Managed Bookmarks IOS";
+const char kManagedBookmarksIOSDescription[] =
+    "When enabled, managed bookmarks set by an enterprise policy can be shown "
+    "in the bookmarks UI on iOS";
+
 const char kMarkHttpAsName[] = "Mark non-secure origins as non-secure";
 const char kMarkHttpAsDescription[] = "Change the UI treatment for HTTP pages";
 
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 7993fba..5e68d1a 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -235,6 +235,11 @@
 extern const char kLockBottomToolbarName[];
 extern const char kLockBottomToolbarDescription[];
 
+// Title and description for the flag to enable ManagedBookmarks enterprise
+// policy on iOS.
+extern const char kManagedBookmarksIOSName[];
+extern const char kManagedBookmarksIOSDescription[];
+
 // Title, description, and options for the MarkHttpAs setting that controls
 // display of omnibox warnings about non-secure pages.
 extern const char kMarkHttpAsName[];
diff --git a/ios/chrome/browser/metrics/BUILD.gn b/ios/chrome/browser/metrics/BUILD.gn
index dc76f8fd..a77f8016 100644
--- a/ios/chrome/browser/metrics/BUILD.gn
+++ b/ios/chrome/browser/metrics/BUILD.gn
@@ -245,6 +245,7 @@
     "//components/metrics:demographic_metrics_provider",
     "//components/metrics_services_manager",
     "//components/strings",
+    "//components/sync/base",
     "//components/ukm",
     "//components/ukm:test_support",
     "//components/unified_consent:unified_consent",
@@ -328,6 +329,7 @@
     "//components/network_time",
     "//components/sync/base",
     "//components/ukm",
+    "//components/ukm:ukm_test_helper",
     "//ios/chrome/app:app_internal",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser",
@@ -343,7 +345,6 @@
     "//ios/chrome/test/earl_grey:test_support",
     "//ios/testing/earl_grey:earl_grey_support",
     "//net:test_support",
-    "//services/metrics/public/cpp:metrics_cpp",
     "//third_party/metrics_proto",
 
     #TODO(crbug.com/1066297): Remove the below line.
@@ -385,14 +386,16 @@
     "//components/network_time",
     "//components/sync/base",
     "//components/ukm",
+    "//components/ukm:ukm_test_helper",
     "//ios/chrome/browser",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/metrics",
     "//ios/chrome/browser/metrics:metrics_internal",
     "//ios/chrome/test/app:test_support",
     "//ios/testing:nserror_support",
-    "//services/metrics/public/cpp:metrics_cpp",
     "//third_party/metrics_proto",
+
+    #TODO(crbug.com/1066297): Remove the below line.
     "//third_party/zlib/google:compression_utils",
   ]
 }
diff --git a/ios/chrome/browser/metrics/demographics_egtest.mm b/ios/chrome/browser/metrics/demographics_egtest.mm
index b6b30819..b67b2846 100644
--- a/ios/chrome/browser/metrics/demographics_egtest.mm
+++ b/ios/chrome/browser/metrics/demographics_egtest.mm
@@ -16,7 +16,6 @@
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
 #import "ios/testing/earl_grey/app_launch_configuration.h"
 #import "ios/testing/earl_grey/earl_grey_test.h"
-#include "third_party/metrics_proto/user_demographics.pb.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -33,7 +32,6 @@
 }  // namespace
 
 @interface DemographicsTestCase : ChromeTestCase
-
 @end
 
 @implementation DemographicsTestCase
@@ -51,25 +49,18 @@
   // Set a network time so that SyncPrefs::GetUserNoisedBirthYearAndGender
   // does not return a UserDemographicsResult for the kCannotGetTime status.
   [MetricsAppInterface setNetworkTimeForTesting];
-
-  // Record a source in the UKM service so that there is data with which to
-  // generate a UKM Report.
-  [self addDummyUKMSource];
-
-  [MetricsAppInterface buildAndStoreUKMLog];
-  GREYAssertTrue([MetricsAppInterface hasUnsentLogs],
-                 @"The UKM service should have unsent logs.");
 }
 
 - (void)tearDown {
+  [ChromeEarlGrey clearSyncServerData];
   [MetricsAppInterface stopOverridingMetricsAndCrashReportingForTesting];
   GREYAssertNil([MetricsAppInterface releaseHistogramTester],
                 @"Failed to release histogram tester.");
   [super tearDown];
 }
 
+#if defined(CHROME_EARL_GREY_2)
 - (AppLaunchConfiguration)appConfigurationForTestCase {
-  NSString* testName = [self getTestName];
   AppLaunchConfiguration config;
 
   // Features are enabled or disabled based on the name of the test that is
@@ -80,40 +71,54 @@
   // Note that in the if statements, @selector(testSomething) is used rather
   // than @"testSomething" because the former checks that the testSomething
   // method exists somewhere--but not necessarily in this class.
-  if ([testName isEqual:NSStringFromSelector(@selector(
-                            testSyncAndRecordUserDemographicsEnabled))]) {
+  if ([self isRunningTest:@selector
+            (testUKMDemographicsReportingWithFeatureEnabled)]) {
     config.features_enabled.push_back(
         metrics::DemographicMetricsProvider::kDemographicMetricsReporting);
     config.features_enabled.push_back(
         ukm::UkmService::kReportUserNoisedUserBirthYearAndGender);
-  } else if ([testName
-                 isEqual:NSStringFromSelector(@selector(
-                             testSyncAndRecordUserDemographicsDisabled))]) {
+  } else if ([self isRunningTest:@selector
+                   (testUKMDemographicsReportingWithFeatureDisabled)]) {
     config.features_disabled.push_back(
         metrics::DemographicMetricsProvider::kDemographicMetricsReporting);
     config.features_disabled.push_back(
         ukm::UkmService::kReportUserNoisedUserBirthYearAndGender);
+  } else if ([self isRunningTest:@selector
+                   (testUMADemographicsReportingWithFeatureEnabled)]) {
+    config.features_enabled.push_back(
+        metrics::DemographicMetricsProvider::kDemographicMetricsReporting);
+  } else if ([self isRunningTest:@selector
+                   (testUMADemographicsReportingWithFeatureDisabled)]) {
+    config.features_disabled.push_back(
+        metrics::DemographicMetricsProvider::kDemographicMetricsReporting);
   }
   return config;
 }
+#endif  // defined(CHROME_EARL_GREY_2)
 
 #pragma mark - Helpers
 
+// Returns YES if the test method name extracted from |selector| matches the
+// name of the currently running test method.
+- (BOOL)isRunningTest:(SEL)selector {
+  return [[self curentTestMethodName] isEqual:NSStringFromSelector(selector)];
+}
+
 // Adds user demographics, which are ModelType::PRIORITY_PREFERENCES, to the
-// fake sync server. The year is the un-noised birth year, and the gender
-// corresponds to the options in UserDemographicsProto::Gender.
+// fake sync server. |rawBirthYear| is the true birth year, pre-noise, and the
+// gender corresponds to the options in UserDemographicsProto::Gender.
 //
 // Also, verifies (A) that before adding the demographics, the server has no
 // priority preferences and (B) that after adding the demographics, the server
 // has one priority preference.
-- (void)addUserDemographicsToSyncServerWithBirthYear:(int)year
+- (void)addUserDemographicsToSyncServerWithBirthYear:(int)rawBirthYear
                                               gender:(int)gender {
   GREYAssertEqual(
       [ChromeEarlGrey
           numberOfSyncEntitiesWithType:syncer::PRIORITY_PREFERENCES],
       0, @"The fake sync server should have no priority preferences.");
 
-  [ChromeEarlGrey addUserDemographicsToSyncServerWithBirthYear:year
+  [ChromeEarlGrey addUserDemographicsToSyncServerWithBirthYear:rawBirthYear
                                                         gender:gender];
 
   GREYAssertEqual(
@@ -157,31 +162,40 @@
                      @"Client ID should be non-zero.");
 }
 
-// Returns the short test name, e.g. "testSomething" of the test that is
-// currently running. The short test name is extracted from the string for the
-// test's name property, e.g. "-[DemographicsTestCase testSomething]".
-- (NSString*)getTestName {
+// Returns the method name, e.g. "testSomething" of the test that is currently
+// running. The name is extracted from the string for the test's name property,
+// e.g. "-[DemographicsTestCase testSomething]".
+- (NSString*)curentTestMethodName {
   int testNameStart = [self.name rangeOfString:@"test"].location;
   return [self.name
       substringWithRange:NSMakeRange(testNameStart,
                                      self.name.length - testNameStart - 1)];
 }
 
+// Adds dummy data,  stores it in the UKM service's UnsentLogStore, and verifies
+// that the UnsentLogStore has an unsent log.
+- (void)buildAndStoreUKMLog {
+  // Record a source in the UKM service so that there is data with which to
+  // generate a UKM Report.
+  [self addDummyUKMSource];
+  [MetricsAppInterface buildAndStoreUKMLog];
+  GREYAssertTrue([MetricsAppInterface hasUnsentUKMLogs],
+                 @"The UKM service should have unsent logs.");
+}
+
 #pragma mark - Tests
 
 // The tests in this file should correspond to the demographics-related tests in
-// //chrome/browser/metrics/ukm_browsertest.cc.
+// //chrome/browser/metrics/ukm_browsertest.cc and
+// //chrome/browser/metrics/metrics_service_user_demographics_browsertest.cc.
 
 // Tests that user demographics are synced, recorded by UKM, and logged in
 // histograms.
 //
 // Corresponds to AddSyncedUserBirthYearAndGenderToProtoData in
 // //chrome/browser/metrics/ukm_browsertest.cc with features enabled.
-- (void)testSyncAndRecordUserDemographicsEnabled {
-#if defined(CHROME_EARL_GREY_1)
-  EARL_GREY_TEST_DISABLED(@"This test relies on EG2 utilities.");
-#endif
-
+#if defined(CHROME_EARL_GREY_2)
+- (void)testUKMDemographicsReportingWithFeatureEnabled {
   // See |appConfigurationForTestCase| for feature set-up. The kUkmFeature is
   // enabled by default.
   GREYAssertTrue([ChromeEarlGrey isDemographicMetricsReportingEnabled] &&
@@ -190,28 +204,29 @@
                      [ChromeEarlGrey isUKMEnabled],
                  @"Failed to enable the requisite features.");
 
+  [self buildAndStoreUKMLog];
+
   GREYAssertTrue([MetricsAppInterface UKMReportHasBirthYear:kTestBirthYear
                                                      gender:kTestGender],
                  @"The report should contain the specified user demographics");
 
-  int successBucket = 0;  // 0 denotes UserDemographicsStatus::kSuccess.
+  const int success =
+      static_cast<int>(syncer::UserDemographicsStatus::kSuccess);
   GREYAssertNil([MetricsAppInterface
                     expectUniqueSampleWithCount:1
-                                      forBucket:successBucket
+                                      forBucket:success
                                    forHistogram:@"UKM.UserDemographics.Status"],
                 @"Unexpected histogram contents");
 }
+#endif  // defined(CHROME_EARL_GREY_2)
 
 // Tests that user demographics are neither recorded by UKM nor logged in
 // histograms when sync is turned on.
 //
 // Corresponds to AddSyncedUserBirthYearAndGenderToProtoData in
 // //chrome/browser/metrics/ukm_browsertest.cc with features disabled.
-- (void)testSyncAndRecordUserDemographicsDisabled {
-#if defined(CHROME_EARL_GREY_1)
-  EARL_GREY_TEST_DISABLED(@"This test relies on EG2 utilities.");
-#endif
-
+#if defined(CHROME_EARL_GREY_2)
+- (void)testUKMDemographicsReportingWithFeatureDisabled {
   // See |appConfigurationForTestCase| for feature set-up. The kUkmFeature is
   // enabled by default.
   GREYAssertFalse([ChromeEarlGrey isDemographicMetricsReportingEnabled],
@@ -222,11 +237,67 @@
   GREYAssertTrue([ChromeEarlGrey isUKMEnabled],
                  @"Failed to enable kUkmFeature.");
 
+  [self buildAndStoreUKMLog];
+
   GREYAssertFalse([MetricsAppInterface UKMReportHasUserDemographics],
                   @"The report should not contain user demographics.");
   GREYAssertNil([MetricsAppInterface expectSum:0
                                   forHistogram:@"UKM.UserDemographics.Status"],
                 @"Unexpected histogram contents.");
 }
+#endif  // defined(CHROME_EARL_GREY_2)
+
+// Tests that user demographics are synced, recorded by UMA, and logged in
+// histograms.
+//
+// Corresponds to AddSyncedUserBirthYearAndGenderToProtoData in
+// //chrome/browser/metrics/metrics_service_user_demographics_browsertest.cc
+// with features enabled.
+#if defined(CHROME_EARL_GREY_2)
+- (void)testUMADemographicsReportingWithFeatureEnabled {
+  // See |appConfigurationForTestCase| for feature set-up. The kUkmFeature is
+  // enabled by default.
+  GREYAssertTrue([ChromeEarlGrey isDemographicMetricsReportingEnabled],
+                 @"Failed to enable kDemographicMetricsReporting.");
+
+  [MetricsAppInterface buildAndStoreUMALog];
+  GREYAssertTrue([MetricsAppInterface hasUnsentUMALogs],
+                 @"The UKM service should have unsent logs.");
+
+  GREYAssertTrue([MetricsAppInterface UMALogHasBirthYear:kTestBirthYear
+                                                  gender:kTestGender],
+                 @"The report should contain the specified user demographics");
+
+  const int success =
+      static_cast<int>(syncer::UserDemographicsStatus::kSuccess);
+  GREYAssertNil([MetricsAppInterface
+                    expectUniqueSampleWithCount:1
+                                      forBucket:success
+                                   forHistogram:@"UMA.UserDemographics.Status"],
+                @"Unexpected histogram contents");
+}
+#endif  // defined(CHROME_EARL_GREY_2)
+
+// Tests that user demographics are neither recorded by UMA nor logged in
+// histograms when sync is turned on.
+//
+// Corresponds to AddSyncedUserBirthYearAndGenderToProtoData in
+// //chrome/browser/metrics/metrics_service_user_demographics_browsertest.cc
+// with features disabled.
+#if defined(CHROME_EARL_GREY_2)
+- (void)testUMADemographicsReportingWithFeatureDisabled {
+  // See |appConfigurationForTestCase| for feature set-up.
+  GREYAssertFalse([ChromeEarlGrey isDemographicMetricsReportingEnabled],
+                  @"Failed to disable kDemographicMetricsReporting.");
+
+  [MetricsAppInterface buildAndStoreUMALog];
+  GREYAssertTrue([MetricsAppInterface hasUnsentUMALogs],
+                 @"The UKM service should have unsent logs.");
+
+  GREYAssertNil([MetricsAppInterface expectSum:0
+                                  forHistogram:@"UMA.UserDemographics.Status"],
+                @"Unexpected histogram contents.");
+}
+#endif  // defined(CHROME_EARL_GREY_2)
 
 @end
diff --git a/ios/chrome/browser/metrics/metrics_app_interface.h b/ios/chrome/browser/metrics/metrics_app_interface.h
index cd78ae03..ca80eb0 100644
--- a/ios/chrome/browser/metrics/metrics_app_interface.h
+++ b/ios/chrome/browser/metrics/metrics_app_interface.h
@@ -32,45 +32,66 @@
 + (void)stopOverridingMetricsAndCrashReportingForTesting;
 + (BOOL)setMetricsAndCrashReportingForTesting:(BOOL)enabled;
 
-// TODO(crbug.com/1066297): Refactor to remove duplicate code.
 // Returns whether UKM recording is |enabled|.
 + (BOOL)checkUKMRecordingEnabled:(BOOL)enabled;
 
 // Returns YES if the ReportUserNoisedUserBirthYearAndGender feature is enabled.
 + (BOOL)isReportUserNoisedUserBirthYearAndGenderEnabled WARN_UNUSED_RESULT;
 
-// TODO(crbug.com/1066297): Refactor to remove duplicate code.
 // Returns the current UKM client ID.
 + (uint64_t)UKMClientID;
 
-// TODO(crbug.com/1066297): Refactor to remove duplicate code.
 // Checks whether a sourceID is registered for UKM.
-+ (BOOL)UKMHasDummySource:(int64_t)sourceId;
++ (BOOL)UKMHasDummySource:(int64_t)sourceID;
 
-// TODO(crbug.com/1066297): Refactor to remove duplicate code.
 // Adds a new sourceID for UKM.
-+ (void)UKMRecordDummySource:(int64_t)sourceId;
++ (void)UKMRecordDummySource:(int64_t)sourceID;
 
 // TODO(crbug.com/1066297): Refactor to remove duplicate code.
 // Sets the network time to approximately now.
 + (void)setNetworkTimeForTesting;
 
 // TODO(crbug.com/1066297): Refactor to remove duplicate code.
-// If new data are available, creates a UKM Report and stores it in the
+// Gets the user's noised birth year, where |rawBirthYear| is the true birth
+// year, pre-noise.
++ (int)noisedBirthYear:(int)rawBirthYear;
+
+// If data are available, creates a UKM Report and stores it in the
 // UKM service's UnsentLogStore.
 + (void)buildAndStoreUKMLog;
 
-// TODO(crbug.com/1066297): Refactor to remove duplicate code.
 // Returns YES if the UKM service has logs to send.
-+ (BOOL)hasUnsentLogs;
++ (BOOL)hasUnsentUKMLogs;
 
+// TODO(crbug.com/1066297): Refactor to remove duplicate code.
 // Returns YES if the UKM service's report has the expected year and gender.
-// Gender corresponds to the options in UserDemographicsProto::Gender.
+// The year is the un-noised birth year, and the gender corresponds to the
+// options in UserDemographicsProto::Gender.
 + (BOOL)UKMReportHasBirthYear:(int)year gender:(int)gender;
 
+// TODO(crbug.com/1066297): Refactor to remove duplicate code.
 // Returns YES if the UKM service's report has user demographics.
 + (BOOL)UKMReportHasUserDemographics;
 
+// TODO(crbug.com/1066297): Refactor to remove duplicate code.
+// If data are available, creates an UMA log and stores it in the
+// MetricsLogStore.
++ (void)buildAndStoreUMALog;
+
+// TODO(crbug.com/1066297): Refactor to remove duplicate code.
+// Returns YES if the metrics service has logs to send.
++ (BOOL)hasUnsentUMALogs;
+
+// TODO(crbug.com/1066297): Refactor to remove duplicate code.
+// Returns YES if the UMA log has the expected year and gender. The year is the
+// un-noised birth year, and the gender corresponds to the proto enum
+// UserDemographicsProto::Gender.
++ (BOOL)UMALogHasBirthYear:(int)year gender:(int)gender;
+
+// TODO(crbug.com/1066297): Refactor to remove duplicate code.
+// Returns YES if the UMA log has user demographics.
++ (BOOL)UMALogHasUserDemographics;
+
 // Creates a chrome_test_util::HistogramTester that will record every histogram
 // sent during test.
 + (NSError*)setupHistogramTester WARN_UNUSED_RESULT;
diff --git a/ios/chrome/browser/metrics/metrics_app_interface.mm b/ios/chrome/browser/metrics/metrics_app_interface.mm
index 8bc0124a..1d23348 100644
--- a/ios/chrome/browser/metrics/metrics_app_interface.mm
+++ b/ios/chrome/browser/metrics/metrics_app_interface.mm
@@ -4,9 +4,8 @@
 
 #include "ios/chrome/browser/metrics/metrics_app_interface.h"
 
-#include <memory>
+#include <string>
 
-#include "base/feature_list.h"
 #include "base/run_loop.h"
 #include "base/stl_util.h"
 #include "base/strings/sys_string_conversions.h"
@@ -14,20 +13,21 @@
 #include "base/time/default_clock.h"
 #include "base/time/default_tick_clock.h"
 #include "base/time/time.h"
+#include "components/metrics/metrics_service.h"
 #include "components/metrics/unsent_log_store.h"
 #include "components/metrics_services_manager/metrics_services_manager.h"
 #include "components/network_time/network_time_tracker.h"
 #include "components/sync/base/pref_names.h"
 #include "components/ukm/ukm_service.h"
+#include "components/ukm/ukm_test_helper.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
 #include "ios/chrome/browser/metrics/ios_chrome_metrics_service_accessor.h"
 #import "ios/chrome/test/app/histogram_test_util.h"
 #import "ios/testing/nserror_util.h"
-#include "services/metrics/public/cpp/ukm_source_id.h"
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
 #include "third_party/metrics_proto/ukm/report.pb.h"
-#include "third_party/metrics_proto/user_demographics.pb.h"
 #include "third_party/zlib/google/compression_utils.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -39,103 +39,35 @@
 bool g_metrics_enabled = false;
 
 chrome_test_util::HistogramTester* g_histogram_tester = nullptr;
+
+ukm::UkmService* GetUkmService() {
+  return GetApplicationContext()->GetMetricsServicesManager()->GetUkmService();
+}
+
+// TODO(crbug.com/1066297): Refactor to remove duplicate code.
+// Returns an UMA log if the metrics service has a staged log.
+std::unique_ptr<metrics::ChromeUserMetricsExtension> GetLastUmaLog() {
+  metrics::MetricsLogStore* const log_store =
+      GetApplicationContext()->GetMetricsService()->LogStoreForTest();
+
+  // Decompress the staged log.
+  std::string uncompressed_log;
+  if (!compression::GzipUncompress(log_store->staged_log(),
+                                   &uncompressed_log)) {
+    return nullptr;
+  }
+
+  // Deserialize and return the log.
+  std::unique_ptr<metrics::ChromeUserMetricsExtension> uma_log =
+      std::make_unique<metrics::ChromeUserMetricsExtension>();
+  if (!uma_log->ParseFromString(uncompressed_log)) {
+    return nullptr;
+  }
+  return uma_log;
+}
+
 }  // namespace
 
-namespace metrics {
-
-// Helper class that provides access to UKM internals.
-// This class is a friend of UKMService and UkmRecorderImpl.
-class UkmEGTestHelper {
- public:
-  UkmEGTestHelper() {}
-  UkmEGTestHelper(const UkmEGTestHelper&) = delete;
-  UkmEGTestHelper& operator=(UkmEGTestHelper&) = delete;
-
-  static bool ukm_enabled() {
-    auto* service = ukm_service();
-    return service ? service->recording_enabled_ : false;
-  }
-
-  static bool IsReportUserNoisedUserBirthYearAndGenderEnabled() {
-    return base::FeatureList::IsEnabled(
-        ukm::UkmService::kReportUserNoisedUserBirthYearAndGender);
-  }
-
-  static uint64_t client_id() {
-    auto* service = ukm_service();
-    return service ? service->client_id_ : 0;
-  }
-
-  static bool HasDummySource(int64_t source_id_as_int64) {
-    auto* service = ukm_service();
-    ukm::SourceId source_id = source_id_as_int64;
-    return service && base::Contains(service->sources(), source_id);
-  }
-
-  static void RecordDummySource(ukm::SourceId source_id_as_int64) {
-    ukm::UkmService* service = ukm_service();
-    ukm::SourceId source_id = source_id_as_int64;
-    if (service)
-      service->UpdateSourceURL(source_id, GURL("http://example.com"));
-  }
-
-  static void BuildAndStoreUkmLog() {
-    ukm::UkmService* service = ukm_service();
-
-    // Wait for initialization to complete before flushing.
-    base::RunLoop run_loop;
-    service->SetInitializationCompleteCallbackForTesting(
-        run_loop.QuitClosure());
-    run_loop.Run();
-
-    service->Flush();
-  }
-
-  static bool HasUnsentLogs() {
-    ukm::UkmService* service = ukm_service();
-    return service->reporting_service_.ukm_log_store()->has_unsent_logs();
-  }
-
-  static std::unique_ptr<ukm::Report> GetUKMReport() {
-    if (!HasUnsentLogs()) {
-      return nullptr;
-    }
-
-    metrics::UnsentLogStore* log_store =
-        ukm_service()->reporting_service_.ukm_log_store();
-    if (log_store->has_staged_log()) {
-      // For testing purposes, we are examining the content of a staged log
-      // without ever sending the log, so discard any previously staged log.
-      log_store->DiscardStagedLog();
-    }
-    log_store->StageNextLog();
-    if (!log_store->has_staged_log()) {
-      return nullptr;
-    }
-
-    std::string uncompressed_log_data;
-    if (!compression::GzipUncompress(log_store->staged_log(),
-                                     &uncompressed_log_data)) {
-      return nullptr;
-    }
-
-    std::unique_ptr<ukm::Report> report = std::make_unique<ukm::Report>();
-    if (!report->ParseFromString(uncompressed_log_data)) {
-      return nullptr;
-    }
-    return report;
-  }
-
- private:
-  static ukm::UkmService* ukm_service() {
-    return GetApplicationContext()
-        ->GetMetricsServicesManager()
-        ->GetUkmService();
-  }
-};
-
-}  // namespace metrics
-
 @implementation MetricsAppInterface : NSObject
 
 + (void)overrideMetricsAndCrashReportingForTesting {
@@ -157,28 +89,32 @@
 }
 
 + (BOOL)checkUKMRecordingEnabled:(BOOL)enabled {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
+
   ConditionBlock condition = ^{
-    return metrics::UkmEGTestHelper::ukm_enabled() == enabled;
+    return ukm_test_helper.IsRecordingEnabled() == enabled;
   };
   return base::test::ios::WaitUntilConditionOrTimeout(
       syncher::kSyncUKMOperationsTimeout, condition);
 }
 
 + (BOOL)isReportUserNoisedUserBirthYearAndGenderEnabled {
-  return metrics::UkmEGTestHelper::
-      IsReportUserNoisedUserBirthYearAndGenderEnabled();
+  return ukm::UkmTestHelper::IsReportUserNoisedUserBirthYearAndGenderEnabled();
 }
 
 + (uint64_t)UKMClientID {
-  return metrics::UkmEGTestHelper::client_id();
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
+  return ukm_test_helper.GetClientId();
 }
 
-+ (BOOL)UKMHasDummySource:(int64_t)sourceId {
-  return metrics::UkmEGTestHelper::HasDummySource(sourceId);
++ (BOOL)UKMHasDummySource:(int64_t)sourceID {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
+  return ukm_test_helper.HasSource(sourceID);
 }
 
 + (void)UKMRecordDummySource:(int64_t)sourceID {
-  metrics::UkmEGTestHelper::RecordDummySource(sourceID);
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
+  ukm_test_helper.RecordSourceForTesting(sourceID);
 }
 
 + (void)setNetworkTimeForTesting {
@@ -195,36 +131,72 @@
       clock.Now() - latency / 2, resolution, latency, tickClock.NowTicks());
 }
 
-+ (void)buildAndStoreUKMLog {
-  metrics::UkmEGTestHelper::BuildAndStoreUkmLog();
-}
-
-+ (BOOL)hasUnsentLogs {
-  return metrics::UkmEGTestHelper::HasUnsentLogs();
-}
-
-+ (BOOL)UKMReportHasBirthYear:(int)year gender:(int)gender {
-  std::unique_ptr<ukm::Report> report =
-      metrics::UkmEGTestHelper::GetUKMReport();
-
++ (int)noisedBirthYear:(int)rawBirthYear {
   int birthYearOffset =
       GetApplicationContext()
           ->GetChromeBrowserStateManager()
           ->GetLastUsedBrowserState()
           ->GetPrefs()
           ->GetInteger(syncer::prefs::kSyncDemographicsBirthYearOffset);
-  int noisedBirthYear = year + birthYearOffset;
+  return rawBirthYear + birthYearOffset;
+}
+
++ (void)buildAndStoreUKMLog {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
+  ukm_test_helper.BuildAndStoreLog();
+}
+
++ (BOOL)hasUnsentUKMLogs {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
+  return ukm_test_helper.HasUnsentLogs();
+}
+
++ (BOOL)UKMReportHasBirthYear:(int)year gender:(int)gender {
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
+  std::unique_ptr<ukm::Report> report = ukm_test_helper.GetUkmReport();
+  int noisedBirthYear = [self noisedBirthYear:year];
 
   return report && gender == report->user_demographics().gender() &&
          noisedBirthYear == report->user_demographics().birth_year();
 }
 
 + (BOOL)UKMReportHasUserDemographics {
-  std::unique_ptr<ukm::Report> report =
-      metrics::UkmEGTestHelper::GetUKMReport();
+  ukm::UkmTestHelper ukm_test_helper(GetUkmService());
+  // TODO(crbug.com/1066910): Maybe ukm::Report*?
+  std::unique_ptr<ukm::Report> report = ukm_test_helper.GetUkmReport();
   return report && report->has_user_demographics();
 }
 
++ (void)buildAndStoreUMALog {
+  GetApplicationContext()->GetMetricsService()->StageCurrentLogForTest();
+}
+
++ (BOOL)hasUnsentUMALogs {
+  return GetApplicationContext()
+      ->GetMetricsService()
+      ->LogStoreForTest()
+      ->has_unsent_logs();
+}
+
++ (BOOL)UMALogHasBirthYear:(int)year gender:(int)gender {
+  if (![self UMALogHasUserDemographics]) {
+    return NO;
+  }
+  std::unique_ptr<metrics::ChromeUserMetricsExtension> log = GetLastUmaLog();
+  int noisedBirthYear = [self noisedBirthYear:year];
+
+  return noisedBirthYear == log->user_demographics().birth_year() &&
+         gender == log->user_demographics().gender();
+}
+
++ (BOOL)UMALogHasUserDemographics {
+  if (![self hasUnsentUMALogs]) {
+    return NO;
+  }
+  std::unique_ptr<metrics::ChromeUserMetricsExtension> log = GetLastUmaLog();
+  return log && log->has_user_demographics();
+}
+
 + (NSError*)setupHistogramTester {
   if (g_histogram_tester) {
     return testing::NSErrorWithLocalizedDescription(
diff --git a/ios/chrome/browser/policy/policy_features.cc b/ios/chrome/browser/policy/policy_features.cc
index 205ae4d..e4e6d85 100644
--- a/ios/chrome/browser/policy/policy_features.cc
+++ b/ios/chrome/browser/policy/policy_features.cc
@@ -9,6 +9,9 @@
 #include "ios/chrome/browser/chrome_switches.h"
 #include "ios/chrome/common/channel_info.h"
 
+const base::Feature kManagedBookmarksIOS{"ManagedBookmarksIOS",
+                                         base::FEATURE_DISABLED_BY_DEFAULT};
+
 namespace {
 
 // Returns true if the current command line contains the
@@ -32,3 +35,16 @@
 bool ShouldInstallEnterprisePolicyHandlers() {
   return IsEnableEnterprisePolicySwitchPresent();
 }
+
+bool ShouldInstallManagedBookmarksPolicyHandler() {
+  // This feature is controlled via the command line because policy must be
+  // initialized before about:flags or field trials. Using a command line flag
+  // is the only way to control this feature at runtime.
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  return command_line->HasSwitch(switches::kInstallManagedBookmarksHandler);
+}
+
+bool IsManagedBookmarksEnabled() {
+  return ShouldInstallManagedBookmarksPolicyHandler() &&
+         base::FeatureList::IsEnabled(kManagedBookmarksIOS);
+}
diff --git a/ios/chrome/browser/policy/policy_features.h b/ios/chrome/browser/policy/policy_features.h
index 50708aa0..c990484a 100644
--- a/ios/chrome/browser/policy/policy_features.h
+++ b/ios/chrome/browser/policy/policy_features.h
@@ -5,6 +5,11 @@
 #ifndef IOS_CHROME_BROWSER_POLICY_POLICY_FEATURES_H_
 #define IOS_CHROME_BROWSER_POLICY_POLICY_FEATURES_H_
 
+#include "base/feature_list.h"
+
+// Feature flag for supporting the ManagedBookmarks enterprise policy on iOS.
+extern const base::Feature kManagedBookmarksIOS;
+
 // Returns true if the core enterprise policy infrastructure is enabled. Does
 // not control whether policy data is parsed and made user visible; that is
 // controlled by |ShouldInstallEnterprisePolicyHandlers()| below.
@@ -14,4 +19,11 @@
 // policy data and make it user visible.
 bool ShouldInstallEnterprisePolicyHandlers();
 
+// Returns true if the ManagedBookmarks policy handler should be installed to
+// parse policy data and make it user visible.
+bool ShouldInstallManagedBookmarksPolicyHandler();
+
+// Returns true if ManagedBookmarks enterprise policy is enabled.
+bool IsManagedBookmarksEnabled();
+
 #endif  // IOS_CHROME_BROWSER_POLICY_POLICY_FEATURES_H_
diff --git a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_coordinator.mm b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_coordinator.mm
index 8095384..ddff3d7 100644
--- a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_coordinator.mm
@@ -124,7 +124,9 @@
   self.unifiedConsentCoordinator.delegate = self;
 
   // Set UnifiedConsentCoordinator properties.
-  self.unifiedConsentCoordinator.selectedIdentity = self.defaultIdentity;
+  if (self.defaultIdentity) {
+    self.unifiedConsentCoordinator.selectedIdentity = self.defaultIdentity;
+  }
   self.unifiedConsentCoordinator.autoOpenIdentityPicker =
       self.logger.promoAction == PromoAction::PROMO_ACTION_NOT_DEFAULT;
 
diff --git a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_view_controller.mm b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_view_controller.mm
index 9aad766..1d74c5a 100644
--- a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_view_controller.mm
@@ -22,32 +22,42 @@
 namespace {
 // Button corner radius.
 const CGFloat kButtonCornerRadius = 8;
-// Inset between button contents and edge.
-const CGFloat kButtonTitleContentInset = 8.0;
+// Gradient height.
+const CGFloat kGradientHeight = 40.;
+// Max size for the user consent view.
+const CGFloat kUserConsentMaxSize = 600.;
+// Image inset for the more button.
+const CGFloat kImageInset = 16.0;
 
 // Layout constants for buttons.
 struct AuthenticationViewConstants {
-  CGFloat GradientHeight;
+  CGFloat PrimaryFontSize;
+  CGFloat SecondaryFontSize;
   CGFloat ButtonHeight;
   CGFloat ButtonHorizontalPadding;
-  CGFloat ButtonTopPadding;
-  CGFloat ButtonBottomPadding;
+  CGFloat ButtonVerticalPadding;
+  CGFloat ButtonTitleContentHorizontalInset;
+  CGFloat ButtonTitleContentVerticalInset;
 };
 
 const AuthenticationViewConstants kCompactConstants = {
-    40,  // GradientHeight
+    24,  // PrimaryFontSize
+    14,  // SecondaryFontSize
     36,  // ButtonHeight
     16,  // ButtonHorizontalPadding
-    16,  // ButtonTopPadding
-    16,  // ButtonBottomPadding
+    16,  // ButtonVerticalPadding
+    16,  // ButtonTitleContentHorizontalInset
+    8,   // ButtonTitleContentVerticalInset
 };
 
 const AuthenticationViewConstants kRegularConstants = {
-    kCompactConstants.GradientHeight,
+    1.5 * kCompactConstants.PrimaryFontSize,
+    1.5 * kCompactConstants.SecondaryFontSize,
     1.5 * kCompactConstants.ButtonHeight,
     32,  // ButtonHorizontalPadding
-    32,  // ButtonTopPadding
-    32,  // ButtonBottomPadding
+    32,  // ButtonVerticalPadding
+    16,  // ButtonTitleContentInset
+    16,  // ButtonTitleContentInset
 };
 
 // The style applied to a button type.
@@ -60,6 +70,9 @@
 
 @interface UserSigninViewController ()
 
+// Container view used to center vertically the user consent view between the
+// top of the controller view and the top of the button view.
+@property(nonatomic, strong) UIView* containerView;
 // Activity indicator used to block the UI when a sign-in operation is in
 // progress.
 @property(nonatomic, strong) MDCActivityIndicator* activityIndicator;
@@ -73,12 +86,22 @@
 @property(nonatomic, assign) BOOL hasUnifiedConsentScreenReachedBottom;
 // Gradient used to hide text that is close to the bottom of the screen. This
 // gives users the hint that there is more to scroll through.
-@property(nonatomic, strong) GradientView* gradientView;
+@property(nonatomic, strong, readonly) GradientView* gradientView;
+// Lists of constraints that need to be activated when the view is in
+// compact size class.
+@property(nonatomic, strong, readonly) NSArray* compactSizeClassConstraints;
+// Lists of constraints that need to be activated when the view is in
+// regular size class.
+@property(nonatomic, strong, readonly) NSArray* regularSizeClassConstraints;
 
 @end
 
 @implementation UserSigninViewController
 
+@synthesize gradientView = _gradientView;
+@synthesize compactSizeClassConstraints = _compactSizeClassConstraints;
+@synthesize regularSizeClassConstraints = _regularSizeClassConstraints;
+
 #pragma mark - Public
 
 - (void)markUnifiedConsentScreenReachedBottom {
@@ -138,52 +161,86 @@
   self.view.backgroundColor = self.systemBackgroundColor;
 
   [self addConfirmationButton];
-  [self embedUserConsentView];
   [self addSkipSigninButton];
+  [self embedUserConsentView];
 
-  [self.view addSubview:self.gradientView];
+  [NSLayoutConstraint activateConstraints:@[
+    // Contraints for the container view. The bottom constraint has to be set
+    // according to the size class.
+    [self.containerView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
+    [self.containerView.leadingAnchor
+        constraintEqualToAnchor:self.view.leadingAnchor],
+    [self.containerView.trailingAnchor
+        constraintEqualToAnchor:self.view.trailingAnchor],
+  ]];
 
-  // The layout constraints should be added at the end once all of the views
-  // have been created.
-  AuthenticationViewConstants constants = self.authenticationViewConstants;
-
-  // Embedded view constraints.
-  AddSameConstraintsWithInsets(
-      self.unifiedConsentViewController.view, self.view.safeAreaLayoutGuide,
-      ChromeDirectionalEdgeInsetsMake(0, 0,
-                                      constants.ButtonHeight +
-                                          constants.ButtonBottomPadding +
-                                          constants.ButtonTopPadding,
-                                      0));
-
-  // Skip sign-in button constraints.
-  AddSameConstraintsToSidesWithInsets(
-      self.skipSigninButton, self.view.safeAreaLayoutGuide,
-      LayoutSides::kBottom | LayoutSides::kLeading,
-      ChromeDirectionalEdgeInsetsMake(0, constants.ButtonHorizontalPadding,
-                                      constants.ButtonBottomPadding, 0));
-
-  // Confirmation button constraints.
-  AddSameConstraintsToSidesWithInsets(
-      self.confirmationButton, self.view.safeAreaLayoutGuide,
-      LayoutSides::kBottom | LayoutSides::kTrailing,
-      ChromeDirectionalEdgeInsetsMake(0, 0, constants.ButtonBottomPadding,
-                                      constants.ButtonHorizontalPadding));
-
-  // Gradient layer constraints.
+  [self.containerView addSubview:self.gradientView];
   self.gradientView.translatesAutoresizingMaskIntoConstraints = NO;
   [NSLayoutConstraint activateConstraints:@[
+    // The gradient view needs to be attatched to the bottom of the user
+    // consent view which contains the scroll view.
     [self.gradientView.bottomAnchor
         constraintEqualToAnchor:self.unifiedConsentViewController.view
                                     .bottomAnchor],
     [self.gradientView.leadingAnchor
-        constraintEqualToAnchor:self.view.leadingAnchor],
+        constraintEqualToAnchor:self.unifiedConsentViewController.view
+                                    .leadingAnchor],
     [self.gradientView.trailingAnchor
-        constraintEqualToAnchor:self.view.trailingAnchor],
-
-    [self.gradientView.heightAnchor
-        constraintEqualToConstant:constants.GradientHeight],
+        constraintEqualToAnchor:self.unifiedConsentViewController.view
+                                    .trailingAnchor],
+    [self.gradientView.heightAnchor constraintEqualToConstant:kGradientHeight],
   ]];
+
+  [self updateViewsAndConstraints];
+}
+
+- (void)viewDidLayoutSubviews {
+  [super viewDidLayoutSubviews];
+  [self updateViewsAndConstraints];
+}
+
+#pragma mark - Constraints
+
+// Generate default constraints based on the constants.
+- (NSMutableArray*)generateConstraintsWithConstants:
+    (AuthenticationViewConstants)constants {
+  NSMutableArray* constraints = [NSMutableArray array];
+  // Confirmation button constraints
+  [constraints addObjectsFromArray:@[
+    [self.view.safeAreaLayoutGuide.trailingAnchor
+        constraintEqualToAnchor:self.confirmationButton.trailingAnchor
+                       constant:constants.ButtonHorizontalPadding],
+    [self.view.safeAreaLayoutGuide.bottomAnchor
+        constraintEqualToAnchor:self.confirmationButton.bottomAnchor
+                       constant:constants.ButtonVerticalPadding],
+  ]];
+  // Skip button constraints
+  [constraints addObjectsFromArray:@[
+    [self.skipSigninButton.leadingAnchor
+        constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor
+                       constant:constants.ButtonHorizontalPadding],
+    [self.view.safeAreaLayoutGuide.bottomAnchor
+        constraintEqualToAnchor:self.skipSigninButton.bottomAnchor
+                       constant:constants.ButtonVerticalPadding],
+  ]];
+  return constraints;
+}
+
+// Updates view sizes and constraints.
+- (void)updateViewsAndConstraints {
+  BOOL isRegularSizeClass = IsRegularXRegularSizeClass(self.traitCollection);
+  UIFontTextStyle fontStyle;
+  if (isRegularSizeClass) {
+    [NSLayoutConstraint deactivateConstraints:self.compactSizeClassConstraints];
+    [NSLayoutConstraint activateConstraints:self.regularSizeClassConstraints];
+    fontStyle = UIFontTextStyleTitle2;
+  } else {
+    [NSLayoutConstraint deactivateConstraints:self.regularSizeClassConstraints];
+    [NSLayoutConstraint activateConstraints:self.compactSizeClassConstraints];
+    fontStyle = UIFontTextStyleSubheadline;
+  }
+  [self applyDefaultSizeWithButton:self.confirmationButton fontStyle:fontStyle];
+  [self applyDefaultSizeWithButton:self.skipSigninButton fontStyle:fontStyle];
 }
 
 #pragma mark - Properties
@@ -221,9 +278,6 @@
   return isRegularSizeClass ? kRegularConstants : kCompactConstants;
 }
 
-// Sets up gradient that masks text when the iOS device size is a compact size.
-// This is to hint to the user that there is additional text below the end of
-// the screen.
 - (UIView*)gradientView {
   if (!_gradientView) {
     _gradientView = [[GradientView alloc] init];
@@ -231,6 +285,79 @@
   return _gradientView;
 }
 
+- (NSArray*)compactSizeClassConstraints {
+  if (!_compactSizeClassConstraints) {
+    NSMutableArray* constraints =
+        [self generateConstraintsWithConstants:kCompactConstants];
+    [constraints addObjectsFromArray:@[
+      // Constraints for the user consent view inside the container view.
+      [self.unifiedConsentViewController.view.topAnchor
+          constraintEqualToAnchor:self.containerView.topAnchor],
+      [self.unifiedConsentViewController.view.bottomAnchor
+          constraintEqualToAnchor:self.containerView.bottomAnchor],
+      [self.unifiedConsentViewController.view.leadingAnchor
+          constraintEqualToAnchor:self.containerView.leadingAnchor],
+      [self.unifiedConsentViewController.view.trailingAnchor
+          constraintEqualToAnchor:self.containerView.trailingAnchor],
+      // Constraint between the container view and the confirmation button.
+      [self.confirmationButton.topAnchor
+          constraintEqualToAnchor:self.containerView.bottomAnchor
+                         constant:kCompactConstants.ButtonVerticalPadding],
+    ]];
+    _compactSizeClassConstraints = constraints;
+  }
+  return _compactSizeClassConstraints;
+}
+
+- (NSArray*)regularSizeClassConstraints {
+  if (!_regularSizeClassConstraints) {
+    NSMutableArray* constraints =
+        [self generateConstraintsWithConstants:kRegularConstants];
+    [constraints addObjectsFromArray:@[
+      // Constraints for the user consent view inside the container view, to
+      // make sure it is never bigger than the container view.
+      [self.unifiedConsentViewController.view.topAnchor
+          constraintGreaterThanOrEqualToAnchor:self.containerView.topAnchor],
+      [self.unifiedConsentViewController.view.bottomAnchor
+          constraintLessThanOrEqualToAnchor:self.containerView.bottomAnchor],
+      [self.unifiedConsentViewController.view.leadingAnchor
+          constraintGreaterThanOrEqualToAnchor:self.containerView
+                                                   .leadingAnchor],
+      [self.unifiedConsentViewController.view.trailingAnchor
+          constraintLessThanOrEqualToAnchor:self.containerView.trailingAnchor],
+      // The user consent view needs to be centered if the container view is
+      // bigger than the max size authorized for the user consent view.
+      [self.unifiedConsentViewController.view.centerXAnchor
+          constraintEqualToAnchor:self.containerView.centerXAnchor],
+      [self.unifiedConsentViewController.view.centerYAnchor
+          constraintEqualToAnchor:self.containerView.centerYAnchor],
+      // Constraint between the container view and the confirmation button.
+      [self.confirmationButton.topAnchor
+          constraintEqualToAnchor:self.containerView.bottomAnchor
+                         constant:kRegularConstants.ButtonVerticalPadding],
+    ]];
+    // Adding constraints to ensure the user consent view has a limited size
+    // on iPad. If the screen is bigger than the max size, those constraints
+    // limit the user consent view.
+    // If the screen is smaller than the max size, those constraints are ignored
+    // since they have a lower priority than the constraints set aboved.
+    NSArray* lowerPriorityConstraints = @[
+      [self.unifiedConsentViewController.view.heightAnchor
+          constraintEqualToConstant:kUserConsentMaxSize],
+      [self.unifiedConsentViewController.view.widthAnchor
+          constraintEqualToConstant:kUserConsentMaxSize],
+    ];
+    for (NSLayoutConstraint* constraints in lowerPriorityConstraints) {
+      // We do not use |UILayoutPriorityDefaultHigh| because it makes some
+      // multiline labels on one line and truncated on iPad.
+      constraints.priority = UILayoutPriorityRequired - 1;
+    }
+    [constraints addObjectsFromArray:lowerPriorityConstraints];
+    _regularSizeClassConstraints = constraints;
+  }
+  return _regularSizeClassConstraints;
+}
+
 #pragma mark - Subviews
 
 // Sets up activity indicator properties and adds it to the user sign-in view.
@@ -254,9 +381,11 @@
   self.unifiedConsentViewController.view
       .translatesAutoresizingMaskIntoConstraints = NO;
 
+  self.containerView = [[UIView alloc] init];
+  self.containerView.translatesAutoresizingMaskIntoConstraints = NO;
+  [self.view addSubview:self.containerView];
   [self addChildViewController:self.unifiedConsentViewController];
-  [self.view insertSubview:self.unifiedConsentViewController.view
-              belowSubview:self.confirmationButton];
+  [self.containerView addSubview:self.unifiedConsentViewController.view];
   [self.unifiedConsentViewController didMoveToParentViewController:self];
 }
 
@@ -269,9 +398,6 @@
   [self addSubviewWithButton:self.confirmationButton];
   // Note that the button style will depend on the user sign-in state.
   [self updatePrimaryButtonStyle];
-  self.confirmationButton.contentEdgeInsets =
-      UIEdgeInsetsMake(kButtonTitleContentInset, kButtonTitleContentInset,
-                       kButtonTitleContentInset, kButtonTitleContentInset);
 }
 
 // Sets up skip sign-in button properties and adds it to the user sign-in view.
@@ -280,21 +406,21 @@
   DCHECK(self.unifiedConsentViewController);
   self.skipSigninButton = [[UIButton alloc] init];
   [self addSubviewWithButton:self.skipSigninButton];
-  [self.skipSigninButton setTitle:self.skipSigninButtonTitle.uppercaseString
+  [self.skipSigninButton setTitle:self.skipSigninButtonTitle
                          forState:UIControlStateNormal];
   [self setSkipSigninStylingWithButton:self.skipSigninButton];
   [self.skipSigninButton addTarget:self
                             action:@selector(onSkipSigninButtonPressed:)
                   forControlEvents:UIControlEventTouchUpInside];
-  self.skipSigninButton.contentEdgeInsets =
-      UIEdgeInsetsMake(kButtonTitleContentInset, kButtonTitleContentInset,
-                       kButtonTitleContentInset, kButtonTitleContentInset);
 }
 
 // Sets up button properties and adds it to view.
 - (void)addSubviewWithButton:(UIButton*)button {
-  button.titleLabel.font =
-      [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline];
+  [button
+      setContentCompressionResistancePriority:UILayoutPriorityRequired
+                                      forAxis:UILayoutConstraintAxisVertical];
+  [button setContentHuggingPriority:UILayoutPriorityRequired
+                            forAxis:UILayoutConstraintAxisVertical];
   [self.view addSubview:button];
   button.translatesAutoresizingMaskIntoConstraints = NO;
 }
@@ -323,6 +449,20 @@
       imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
   [button setImage:buttonImage forState:UIControlStateNormal];
   [self setSkipSigninStylingWithButton:button];
+  button.imageEdgeInsets = UIEdgeInsetsMake(0, -kImageInset, 0, 0);
+}
+
+// Applies font and inset to |button| according to the current size class.
+- (void)applyDefaultSizeWithButton:(UIButton*)button
+                         fontStyle:(UIFontTextStyle)fontStyle {
+  const AuthenticationViewConstants& constants =
+      self.authenticationViewConstants;
+  CGFloat horizontalContentInset = constants.ButtonTitleContentHorizontalInset;
+  CGFloat verticalContentInset = constants.ButtonTitleContentVerticalInset;
+  button.contentEdgeInsets =
+      UIEdgeInsetsMake(verticalContentInset, horizontalContentInset,
+                       verticalContentInset, horizontalContentInset);
+  button.titleLabel.font = [UIFont preferredFontForTextStyle:fontStyle];
 }
 
 #pragma mark - Events
diff --git a/ios/chrome/browser/ui/settings/BUILD.gn b/ios/chrome/browser/ui/settings/BUILD.gn
index 6dc9484f..132cd17 100644
--- a/ios/chrome/browser/ui/settings/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/BUILD.gn
@@ -228,6 +228,7 @@
     "//ios/chrome/browser/browser_state:test_support",
     "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/prefs:browser_prefs",
+    "//ios/chrome/browser/search_engines",
     "//ios/chrome/browser/signin",
     "//ios/chrome/browser/signin:test_support",
     "//ios/chrome/browser/sync",
@@ -336,12 +337,14 @@
     "//components/content_settings/core/common",
     "//components/metrics",
     "//components/prefs",
+    "//components/search_engines",
     "//ios/chrome/app:app_internal",
     "//ios/chrome/browser",
     "//ios/chrome/browser:pref_names",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/content_settings",
     "//ios/chrome/browser/content_settings",
+    "//ios/chrome/browser/search_engines",
     "//ios/chrome/test/app:test_support",
     "//third_party/breakpad:client",
   ]
@@ -432,10 +435,12 @@
     "//components/content_settings/core/browser",
     "//components/metrics",
     "//components/prefs",
+    "//components/search_engines",
     "//ios/chrome/app:app_internal",
     "//ios/chrome/browser",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/content_settings",
+    "//ios/chrome/browser/search_engines",
     "//ios/chrome/test/app:test_support",
     "//third_party/breakpad:client",
   ]
diff --git a/ios/chrome/browser/ui/settings/search_engine_settings_egtest.mm b/ios/chrome/browser/ui/settings/search_engine_settings_egtest.mm
index 4592a42..74dc9dfdd 100644
--- a/ios/chrome/browser/ui/settings/search_engine_settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/search_engine_settings_egtest.mm
@@ -4,6 +4,7 @@
 
 #include "base/strings/sys_string_conversions.h"
 #import "base/test/ios/wait_util.h"
+#import "ios/chrome/browser/ui/settings/settings_app_interface.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
@@ -25,6 +26,8 @@
 const char kOpenSearch[] = "/opensearch.xml";
 const char kSearchURL[] = "/search?q=";
 const char kCustomSearchEngineName[] = "Custom Search Engine";
+const char kGoogleURL[] = "google";
+const char kYahooURL[] = "yahoo";
 
 NSString* GetCustomeSearchEngineLabel() {
   return [NSString stringWithFormat:@"%s, 127.0.0.1", kCustomSearchEngineName];
@@ -34,6 +37,21 @@
   return std::string(kSearchURL) + "example";
 }
 
+// Responses for different search engine. The name of the search engine is
+// displayed on the page.
+std::unique_ptr<net::test_server::HttpResponse> SearchResponse(
+    const net::test_server::HttpRequest& request) {
+  std::unique_ptr<net::test_server::BasicHttpResponse> http_response =
+      std::make_unique<net::test_server::BasicHttpResponse>();
+  http_response->set_code(net::HTTP_OK);
+  if (request.GetURL().path().find(kGoogleURL) != std::string::npos) {
+    http_response->set_content("<body>" + std::string(kGoogleURL) + "</body>");
+  } else if (request.GetURL().path().find(kYahooURL) != std::string::npos) {
+    http_response->set_content("<body>" + std::string(kYahooURL) + "</body>");
+  }
+  return std::move(http_response);
+}
+
 // Responses for the test http server. |server_url| is the URL of the server,
 // used for absolute URL in the response. |open_search_queried| is set to true
 // when the OpenSearchDescription is queried.
@@ -83,6 +101,69 @@
 
 @implementation SearchEngineSettingsTestCase
 
+- (void)setUp {
+  [super setUp];
+  [SettingsAppInterface resetSearchEngine];
+}
+
+- (void)tearDown {
+  [SettingsAppInterface resetSearchEngine];
+}
+
+// Tests that when changing the default search engine, the URL used for the
+// search is updated.
+- (void)testChangeSearchEngine {
+  self.testServer->RegisterRequestHandler(base::Bind(&SearchResponse));
+  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
+
+  GURL url = self.testServer->GetURL(kPageURL);
+  NSString* port = base::SysUTF8ToNSString(url.port());
+
+  NSArray<NSString*>* hosts = @[
+    base::SysUTF8ToNSString(kGoogleURL), base::SysUTF8ToNSString(kYahooURL)
+  ];
+
+  [SettingsAppInterface addURLRewriterForHosts:hosts onPort:port];
+
+  // Search on Google.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
+      performAction:grey_tap()];
+  [ChromeEarlGrey
+      waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
+      performAction:grey_typeText([@"test" stringByAppendingString:@"\n"])];
+
+  [ChromeEarlGrey waitForWebStateContainingText:kGoogleURL];
+
+  // Change default search engine to Yahoo.
+  [ChromeEarlGreyUI openSettingsMenu];
+  [[EarlGrey
+      selectElementWithMatcher:chrome_test_util::SettingsSearchEngineButton()]
+      performAction:grey_tap()];
+
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"Yahoo!")]
+      performAction:grey_tap()];
+
+  [[EarlGrey
+      selectElementWithMatcher:chrome_test_util::SettingsMenuBackButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::SettingsDoneButton()]
+      performAction:grey_tap()];
+
+  [SettingsAppInterface addURLRewriterForHosts:hosts onPort:port];
+
+  // Search on Yahoo.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
+      performAction:grey_tap()];
+  [ChromeEarlGrey
+      waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()];
+
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
+      performAction:grey_typeText([@"test" stringByAppendingString:@"\n"])];
+
+  [ChromeEarlGrey waitForWebStateContainingText:kYahooURL];
+}
+
 // Deletes a custom search engine by swiping and tapping on the "Delete" button.
 - (void)testDeleteCustomSearchEngineSwipeAndTap {
   if (@available(iOS 13, *)) {
diff --git a/ios/chrome/browser/ui/settings/settings_app_interface.h b/ios/chrome/browser/ui/settings/settings_app_interface.h
index 204186d..58c2a17 100644
--- a/ios/chrome/browser/ui/settings/settings_app_interface.h
+++ b/ios/chrome/browser/ui/settings/settings_app_interface.h
@@ -50,6 +50,15 @@
 // Returns YES if keyboard commands were seen.
 + (BOOL)settingsRegisteredKeyboardCommands;
 
+// Resets the default search engine to Google.
++ (void)resetSearchEngine;
+
+// Adds a URL rewriter to replace all requests having their host containing a
+// string |host| from |hosts|. Those URL are rewritten to
+// 127.0.0.1:<port>/<host>.
++ (void)addURLRewriterForHosts:(NSArray<NSString*>*)hosts
+                        onPort:(NSString*)port;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_SETTINGS_APP_INTERFACE_H_
diff --git a/ios/chrome/browser/ui/settings/settings_app_interface.mm b/ios/chrome/browser/ui/settings/settings_app_interface.mm
index 85a12b5..8e1fefa0 100644
--- a/ios/chrome/browser/ui/settings/settings_app_interface.mm
+++ b/ios/chrome/browser/ui/settings/settings_app_interface.mm
@@ -4,21 +4,47 @@
 
 #import "ios/chrome/browser/ui/settings/settings_app_interface.h"
 
+#include "base/strings/sys_string_conversions.h"
 #include "components/browsing_data/core/pref_names.h"
 #include "components/metrics/metrics_pref_names.h"
 #include "components/metrics/metrics_service.h"
 #include "components/prefs/pref_member.h"
 #include "components/prefs/pref_service.h"
+#include "components/search_engines/template_url_service.h"
 #import "ios/chrome/app/main_controller.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "ios/chrome/browser/pref_names.h"
+#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
+#import "ios/chrome/test/app/tab_test_util.h"
+#import "ios/web/public/navigation/navigation_manager.h"
+#import "ios/web/public/web_state.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+namespace {
+
+std::vector<std::string> listHosts;
+std::string portForRewrite;
+
+bool HostToLocalHostRewrite(GURL* url, web::BrowserState* browser_state) {
+  DCHECK(url);
+  for (std::string host : listHosts) {
+    if (url->host().find(host) != std::string::npos) {
+      *url =
+          GURL(std::string("http://127.0.0.1:") + portForRewrite + "/" + host);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+}  // namespace
+
 // Test specific helpers for settings_egtest.mm.
 @implementation SettingsAppInterface : NSObject
 
@@ -92,4 +118,26 @@
   return viewController.presentedViewController.keyCommands != nil;
 }
 
++ (void)resetSearchEngine {
+  TemplateURLService* service =
+      ios::TemplateURLServiceFactory::GetForBrowserState(
+          chrome_test_util::GetOriginalBrowserState());
+
+  TemplateURL* templateURL = service->GetTemplateURLForHost("google.com");
+  service->SetUserSelectedDefaultSearchProvider(templateURL);
+}
+
++ (void)addURLRewriterForHosts:(NSArray<NSString*>*)hosts
+                        onPort:(NSString*)port {
+  listHosts.clear();
+  for (NSString* host in hosts) {
+    listHosts.push_back(base::SysNSStringToUTF8(host));
+  }
+  portForRewrite = base::SysNSStringToUTF8(port);
+
+  chrome_test_util::GetCurrentWebState()
+      ->GetNavigationManager()
+      ->AddTransientURLRewriter(&HostToLocalHostRewrite);
+}
+
 @end
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.h b/ios/chrome/test/earl_grey/chrome_earl_grey.h
index df06f61..0f8b15c 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.h
@@ -155,10 +155,10 @@
 // Stops the sync server. The server should be running when calling this.
 - (void)stopSync;
 
-// Injects user demographics into the fake sync server. The year is the
-// un-noised birth year, and the gender corresponds to the options in
+// Injects user demographics into the fake sync server. |rawBirthYear| is the
+// true birth year, pre-noise, and the gender corresponds to the proto enum
 // UserDemographicsProto::Gender.
-- (void)addUserDemographicsToSyncServerWithBirthYear:(int)birthYear
+- (void)addUserDemographicsToSyncServerWithBirthYear:(int)rawBirthYear
                                               gender:(int)gender;
 
 // Clears the autofill profile for the given |GUID|.
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.mm b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
index 33381072..868763f 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
@@ -621,10 +621,10 @@
   [ChromeEarlGreyAppInterface stopSync];
 }
 
-- (void)addUserDemographicsToSyncServerWithBirthYear:(int)birthYear
+- (void)addUserDemographicsToSyncServerWithBirthYear:(int)rawBirthYear
                                               gender:(int)gender {
   [ChromeEarlGreyAppInterface
-      addUserDemographicsToSyncServerWithBirthYear:birthYear
+      addUserDemographicsToSyncServerWithBirthYear:rawBirthYear
                                             gender:gender];
 }
 
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
index cbf344e..2b788c6 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
@@ -305,10 +305,10 @@
 // Triggers a sync cycle for a |type|.
 + (void)triggerSyncCycleForType:(syncer::ModelType)type;
 
-// Injects user demographics into the fake sync server. The year is the
-// un-noised birth year, and the gender corresponds to the options in
-// UserDemographicsProto::Gender..
-+ (void)addUserDemographicsToSyncServerWithBirthYear:(int)birthYear
+// Injects user demographics into the fake sync server. |rawBirthYear| is the
+// true birth year, pre-noise, and the gender corresponds to the proto enum
+// UserDemographicsProto::Gender.
++ (void)addUserDemographicsToSyncServerWithBirthYear:(int)rawBirthYear
                                               gender:(int)gender;
 
 // Clears the autofill profile for the given |GUID|.
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
index 6ef32ff..6db90cb 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -500,9 +500,9 @@
   chrome_test_util::TriggerSyncCycle(type);
 }
 
-+ (void)addUserDemographicsToSyncServerWithBirthYear:(int)birthYear
++ (void)addUserDemographicsToSyncServerWithBirthYear:(int)rawBirthYear
                                               gender:(int)gender {
-  chrome_test_util::AddUserDemographicsToSyncServer(birthYear, gender);
+  chrome_test_util::AddUserDemographicsToSyncServer(rawBirthYear, gender);
 }
 
 + (void)clearAutofillProfileWithGUID:(NSString*)GUID {
diff --git a/ios/third_party/webkit/BUILD.gn b/ios/third_party/webkit/BUILD.gn
index d360f8d1..09de805 100644
--- a/ios/third_party/webkit/BUILD.gn
+++ b/ios/third_party/webkit/BUILD.gn
@@ -18,14 +18,15 @@
 }
 
 if (_build_custom_webkit) {
-  # WebKit is built from source using xcodebuild (invoked via the build_webkit.py wrapper
-  # script).  The WebKit build places its output in a sub-directory named
-  # "Debug-iphonesimulator" for iOS builds and "Debug" for macOS builds.
+  # WebKit is built from source using xcodebuild (invoked via the
+  # build_webkit.py wrapper script).  The WebKit build places its output in a
+  # sub-directory named "Debug-iphonesimulator" for debug iOS builds and
+  # "Release" for release macOS builds.
   _webkit_ios_out_base_dir = "$target_out_dir/iOS"
   _webkit_mac_out_base_dir = "$target_out_dir/macOS"
 
   _webkit_ios_xcodebuild_out_dir_name = "Debug-iphonesimulator"
-  _webkit_mac_xcodebuild_out_dir_name = "Debug"
+  _webkit_mac_xcodebuild_out_dir_name = "Release"
 
   _webkit_ios_out_product_dir =
       "$_webkit_ios_out_base_dir/$_webkit_ios_xcodebuild_out_dir_name"
@@ -134,9 +135,24 @@
         [ "{{bundle_contents_dir}}/WebKitFrameworks/{{source_file_part}}" ]
   }
 
-  copy("copy_webkit_mac_minibrowser") {
-    sources = [ "$_webkit_mac_out_product_dir" ]
+  action("copy_webkit_mac_minibrowser") {
+    script = "copy_webkit_for_clusterfuzz.py"
+
+    sources = [
+      "$_webkit_mac_out_product_dir",
+      "run-clusterfuzz.sh",
+    ]
     outputs = [ "$root_out_dir/WebKitMacOS" ]
+
+    args = [
+      "--output",
+      rebase_path(outputs[0], root_build_dir),
+      "--webkit_build",
+      rebase_path(sources[0], root_build_dir),
+      "--clusterfuzz_script",
+      rebase_path(sources[1], root_build_dir),
+    ]
+
     public_deps = [ ":compile_webkit_mac_minibrowser" ]
   }
 }
diff --git a/ios/third_party/webkit/copy_webkit_for_clusterfuzz.py b/ios/third_party/webkit/copy_webkit_for_clusterfuzz.py
new file mode 100644
index 0000000..0dcc558
--- /dev/null
+++ b/ios/third_party/webkit/copy_webkit_for_clusterfuzz.py
@@ -0,0 +1,34 @@
+# 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.
+
+import argparse
+import os
+import shutil
+import sys
+
+
+def main():
+  description = 'Packages WebKit build for Clusterfuzz.'
+  parser = argparse.ArgumentParser(description=description)
+  parser.add_argument('--output',
+                    help='Output directory for build products.')
+  parser.add_argument('--webkit_build',
+                      help='WebKit build directory to copy.')
+  parser.add_argument('--clusterfuzz_script',
+                      help='Clusterfuzz script to copy.')
+
+  opts = parser.parse_args()
+
+  if os.path.exists(opts.output):
+    shutil.rmtree(opts.output)
+
+  shutil.copytree(opts.webkit_build, opts.output, symlinks=True)
+  shutil.copyfile(
+        opts.clusterfuzz_script,
+        os.path.join(opts.output,
+                     os.path.basename(opts.clusterfuzz_script)))
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/ios/web/navigation/navigation_item_impl.mm b/ios/web/navigation/navigation_item_impl.mm
index e43096e..a1757bf 100644
--- a/ios/web/navigation/navigation_item_impl.mm
+++ b/ios/web/navigation/navigation_item_impl.mm
@@ -343,12 +343,17 @@
 }
 
 void NavigationItemImpl::RestoreStateFromItem(NavigationItem* other) {
-  // Only restore the UserAgent type and the page display state. The other
-  // headers might not make sense after creating a new navigation to the page.
+  // Restore the UserAgent type in any case, as if the URLs are different it
+  // might mean that |this| is a next navigation. The page display state and the
+  // virtual URL only make sense if it is the same item. The other headers might
+  // not make sense after creating a new navigation to the page.
   if (other->GetUserAgentForInheritance() != UserAgentType::NONE) {
     SetUserAgentType(other->GetUserAgentForInheritance());
   }
-  SetPageDisplayState(other->GetPageDisplayState());
+  if (url_ == other->GetURL()) {
+    SetPageDisplayState(other->GetPageDisplayState());
+    SetVirtualURL(other->GetVirtualURL());
+  }
 }
 
 ErrorRetryStateMachine& NavigationItemImpl::error_retry_state_machine() {
diff --git a/ios/web/navigation/navigation_item_impl_unittest.mm b/ios/web/navigation/navigation_item_impl_unittest.mm
index e09fbb4c..63df409 100644
--- a/ios/web/navigation/navigation_item_impl_unittest.mm
+++ b/ios/web/navigation/navigation_item_impl_unittest.mm
@@ -210,5 +210,39 @@
   EXPECT_EQ(UserAgentType::DESKTOP, item_->GetUserAgentForInheritance());
 }
 
+// Tests that RestoreStateFromItem correctly restore the state.
+TEST_F(NavigationItemTest, RestoreState) {
+  NavigationItemImpl other_item;
+  other_item.SetUserAgentType(UserAgentType::DESKTOP);
+  PageDisplayState display_state;
+  display_state.set_scroll_state(
+      PageScrollState(CGPointMake(0, 10), UIEdgeInsetsMake(10, 10, 2, 2)));
+  other_item.SetPageDisplayState(display_state);
+  other_item.SetURL(GURL("www.otherurl.com"));
+  other_item.SetVirtualURL(GURL("www.virtual.com"));
+
+  ASSERT_NE(other_item.GetURL(), item_->GetURL());
+
+  // With a different URL, only the UserAgent should be restored.
+  item_->RestoreStateFromItem(&other_item);
+  EXPECT_EQ(other_item.GetUserAgentForInheritance(),
+            item_->GetUserAgentForInheritance());
+  EXPECT_NE(other_item.GetPageDisplayState(), item_->GetPageDisplayState());
+  EXPECT_NE(other_item.GetVirtualURL(), item_->GetVirtualURL());
+
+  NavigationItemImpl other_item2;
+  other_item2.SetUserAgentType(UserAgentType::DESKTOP);
+  other_item2.SetPageDisplayState(display_state);
+  other_item2.SetURL(item_->GetURL());
+  other_item2.SetVirtualURL(GURL("www.virtual.com"));
+
+  // Same URL, everything is restored.
+  item_->RestoreStateFromItem(&other_item2);
+  EXPECT_EQ(other_item2.GetUserAgentForInheritance(),
+            item_->GetUserAgentForInheritance());
+  EXPECT_EQ(other_item2.GetPageDisplayState(), item_->GetPageDisplayState());
+  EXPECT_EQ(other_item2.GetVirtualURL(), item_->GetVirtualURL());
+}
+
 }  // namespace
 }  // namespace web
diff --git a/ios/web/navigation/navigation_manager_impl_unittest.mm b/ios/web/navigation/navigation_manager_impl_unittest.mm
index 9da4da0a..ece3cb2 100644
--- a/ios/web/navigation/navigation_manager_impl_unittest.mm
+++ b/ios/web/navigation/navigation_manager_impl_unittest.mm
@@ -106,6 +106,7 @@
 // Data holder for the informations to be restored in the items.
 struct ItemInfoToBeRestored {
   GURL url;
+  GURL virtual_url;
   UserAgentType user_agent;
   PageDisplayState display_state;
 };
@@ -1754,17 +1755,20 @@
 // Tests that Restore() creates the correct navigation state.
 TEST_F(NavigationManagerTest, Restore) {
   ItemInfoToBeRestored restore_information[3];
-  restore_information[0] = {GURL("http://www.url.com/0"), UserAgentType::MOBILE,
+  restore_information[0] = {GURL("http://www.url.com/0"),
+                            GURL("http://virtual/0"), UserAgentType::MOBILE,
                             PageDisplayState()};
-  restore_information[1] = {GURL("http://www.url.com/1"),
+  restore_information[1] = {GURL("http://www.url.com/1"), GURL(),
                             UserAgentType::AUTOMATIC, PageDisplayState()};
   restore_information[2] = {GURL("http://www.url.com/2"),
-                            UserAgentType::DESKTOP, PageDisplayState()};
+                            GURL("http://virtual/2"), UserAgentType::DESKTOP,
+                            PageDisplayState()};
 
   std::vector<std::unique_ptr<NavigationItem>> items;
   for (size_t index = 0; index < base::size(restore_information); ++index) {
     items.push_back(NavigationItem::Create());
     items.back()->SetURL(restore_information[index].url);
+    items.back()->SetVirtualURL(restore_information[index].virtual_url);
     items.back()->SetUserAgentType(restore_information[index].user_agent);
     items.back()->SetPageDisplayState(restore_information[index].display_state);
   }
@@ -1791,7 +1795,7 @@
   GURL pending_url = pending_item->GetURL();
   EXPECT_TRUE(pending_url.SchemeIsFile());
   EXPECT_EQ("restore_session.html", pending_url.ExtractFileName());
-  EXPECT_EQ("http://www.url.com/0", pending_item->GetVirtualURL());
+  EXPECT_EQ("http://virtual/0", pending_item->GetVirtualURL());
   navigation_manager()->OnNavigationStarted(pending_url);
 
   // Simulate the end effect of loading the restore session URL in web view.
@@ -1814,6 +1818,10 @@
   for (size_t i = 0; i < base::size(restore_information); ++i) {
     NavigationItem* navigation_item = navigation_manager()->GetItemAtIndex(i);
     EXPECT_EQ(restore_information[i].url, navigation_item->GetURL());
+    if (!restore_information[i].virtual_url.is_empty()) {
+      EXPECT_EQ(restore_information[i].virtual_url,
+                navigation_item->GetVirtualURL());
+    }
     EXPECT_EQ(restore_information[i].user_agent,
               navigation_item->GetUserAgentForInheritance());
     EXPECT_EQ(restore_information[i].display_state,
diff --git a/media/gpu/vaapi/vaapi_video_encode_accelerator.cc b/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
index 234754e1..8613efa2 100644
--- a/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
@@ -444,7 +444,9 @@
                      num_frames_in_flight_, expected_input_coded_size_,
                      output_buffer_byte_size_));
 
-  encoder_info_.scaling_settings = encoder_->GetScalingSettings();
+  // TODO(crbug.com/1034686): Set ScalingSettings causes getStats() hangs.
+  // Investigate and fix the issue.
+  // encoder_info_.scaling_settings = encoder_->GetScalingSettings();
 
   // TODO(crbug.com/1030199): VaapiVideoEncodeAccelerator doesn't support either
   // temporal-SVC or spatial-SVC. Update |fps_allocation| properly once they are
diff --git a/media/mojo/README.md b/media/mojo/README.md
index bf6c42f..c91b3498 100644
--- a/media/mojo/README.md
+++ b/media/mojo/README.md
@@ -318,11 +318,6 @@
 interface. In `MediaService` and `CdmService`, remote media components get
 services from the through **secure auxiliary services**.
 
-Note that as a `service_manager::Service`, `MediaService` and `CdmService` can
-always connect to other `service_manager::Service` hosted by the service_manager
-through the `Connector` interface. However, these are generic services that
-doesn’t belong to any individual `RenderFrame`, or even user profile.
-
 Some services do require `RenderFrame` or user profile identity, e.g. file
 system. Since media components all belong to a given `RenderFrame`, we must
 maintain the frame identity when accessing these services for security reasons.
@@ -330,10 +325,12 @@
 base class for all secure auxiliary services to help manage the lifetime of
 these services (e.g. to handle navigation).
 
-In `MediaInterfaceProxy`, when we request `media::mojom::InterfaceFactory` in
-the `MediaService` or `CdmService`, we call `GetFrameServices()` to configure
-which secure auxiliary services are exposed to the remote components over the
-separate `blink::mojom::BrowserInterfaceBroker`.
+When a `MediaInterfaceProxy` is created, in addition to providing the
+`media::mojom::InterfaceFactory`, the `RenderFrame` is provisioned with a
+`media::mojom::FrameInterfaceFactory` that exposes these secure auxiliary
+services on a per-frame basis. The `FrameInterfaceFactory` directly provides
+services from //content, and it provides a way for //content embedders to
+register additional auxiliary services via the `BindEmbedderReceiver()` method.
 
 Currently only the remote CDM needs secure auxiliary services. This is a list of
 currently supported services:
diff --git a/media/mojo/mojom/BUILD.gn b/media/mojo/mojom/BUILD.gn
index 36d90ec..9134ea2 100644
--- a/media/mojo/mojom/BUILD.gn
+++ b/media/mojo/mojom/BUILD.gn
@@ -23,6 +23,7 @@
     "decryptor.mojom",
     "demuxer_stream.mojom",
     "display_media_information.mojom",
+    "frame_interface_factory.mojom",
     "interface_factory.mojom",
     "key_system_support.mojom",
     "media_log.mojom",
diff --git a/media/mojo/mojom/cdm_service.mojom b/media/mojo/mojom/cdm_service.mojom
index a5402a7..e154033 100644
--- a/media/mojo/mojom/cdm_service.mojom
+++ b/media/mojo/mojom/cdm_service.mojom
@@ -5,6 +5,7 @@
 module media.mojom;
 
 import "media/mojo/mojom/content_decryption_module.mojom";
+import "media/mojo/mojom/frame_interface_factory.mojom";
 import "mojo/public/mojom/base/file_path.mojom";
 import "services/service_manager/public/mojom/interface_provider.mojom";
 
@@ -28,11 +29,12 @@
           [EnableIf=is_mac]
           pending_remote<SeatbeltExtensionTokenProvider>? token_provider);
 
-  // Requests an CdmFactory. |host_interfaces| can optionally be used to provide
-  // interfaces hosted by the caller to the remote CdmFactory implementation.
+  // Requests an CdmFactory. |frame_interfaces| can optionally be used to
+  // provide interfaces hosted by the caller to the remote CdmFactory
+  // implementation.
   CreateCdmFactory(
       pending_receiver<CdmFactory> factory,
-      pending_remote<service_manager.mojom.InterfaceProvider> host_interfaces);
+      pending_remote<FrameInterfaceFactory> frame_interfaces);
 };
 
 // An interface to provide a sandbox seatbelt extension token synchronously.
diff --git a/media/mojo/mojom/frame_interface_factory.mojom b/media/mojo/mojom/frame_interface_factory.mojom
new file mode 100644
index 0000000..32e5f13
--- /dev/null
+++ b/media/mojo/mojom/frame_interface_factory.mojom
@@ -0,0 +1,30 @@
+// 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.
+
+module media.mojom;
+
+[EnableIf=enable_cdm_proxy]
+import "media/mojo/mojom/cdm_proxy.mojom";
+import "media/mojo/mojom/cdm_storage.mojom";
+import "media/mojo/mojom/provision_fetcher.mojom";
+import "mojo/public/mojom/base/generic_pending_receiver.mojom";
+
+// A factory for acquiring media mojo interfaces that are bound to a
+// RenderFrameHost.
+interface FrameInterfaceFactory {
+  // Binds the ProvisionFetcher for the frame.
+  CreateProvisionFetcher(pending_receiver<ProvisionFetcher> provision_fetcher);
+
+  // Binds the CdmStorage for the frame. This requires that the frame have
+  // CDM storage available.
+  CreateCdmStorage(pending_receiver<CdmStorage> cdm_storage);
+
+  /// Binds the CdmProxy for the frame.
+  [EnableIf=enable_cdm_proxy]
+  CreateCdmProxy(pending_receiver<CdmProxy> cdm_proxy);
+
+  // Binds a generic media frame-bound interface. This is to allow //content
+  // embedders to provide additional interfaces.
+  BindEmbedderReceiver(mojo_base.mojom.GenericPendingReceiver receiver);
+};
diff --git a/media/mojo/mojom/media_service.mojom b/media/mojo/mojom/media_service.mojom
index 995e6c5..738b54a 100644
--- a/media/mojo/mojom/media_service.mojom
+++ b/media/mojo/mojom/media_service.mojom
@@ -4,18 +4,18 @@
 
 module media.mojom;
 
+import "media/mojo/mojom/frame_interface_factory.mojom";
 import "media/mojo/mojom/interface_factory.mojom";
-import "services/service_manager/public/mojom/interface_provider.mojom";
 
 // A service to provide media InterfaceFactory, typically to the media pipeline
 // running in the renderer process. The service itself runs in the process
 // specified by the |mojo_media_host| gn build flag. The service is always
 // connected from the browser process.
 interface MediaService {
-  // Requests an InterfaceFactory. |host_interfaces| can optionally be used to
+  // Requests an InterfaceFactory. |frame_interfaces| can optionally be used to
   // provide interfaces hosted by the caller to the remote InterfaceFactory
   // implementation.
   CreateInterfaceFactory(
       pending_receiver<InterfaceFactory> factory,
-      pending_remote<service_manager.mojom.InterfaceProvider> host_interfaces);
+      pending_remote<FrameInterfaceFactory> frame_interfaces);
 };
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc b/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc
index 2c7ac94..cfd51605 100644
--- a/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc
+++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc
@@ -87,8 +87,7 @@
   ::media::VideoEncoderInfo input;
   input.implementation_name = "FakeVideoEncodeAccelerator";
   // Scaling settings.
-  input.scaling_settings.min_qp = 12;
-  input.scaling_settings.max_qp = 123;
+  input.scaling_settings = ::media::ScalingSettings(12, 123);
   // FPS allocation.
   for (size_t i = 0; i < ::media::VideoEncoderInfo::kMaxSpatialLayers; ++i)
     input.fps_allocation[i] = {5, 5, 10};
diff --git a/media/mojo/mojom/video_encoder_info.mojom b/media/mojo/mojom/video_encoder_info.mojom
index e02bead..a299ca7 100644
--- a/media/mojo/mojom/video_encoder_info.mojom
+++ b/media/mojo/mojom/video_encoder_info.mojom
@@ -26,7 +26,7 @@
   bool is_hardware_accelerated;
   bool supports_simulcast;
 
-  ScalingSettings scaling_settings;
+  ScalingSettings? scaling_settings;
   // This array size is equal to media::VideoEncoderInfo::kMaxSpatialLayers.
   array<array<uint8>, 5> fps_allocation;
   array<ResolutionBitrateLimit> resolution_bitrate_limits;
diff --git a/media/mojo/mojom/video_encoder_info_mojom_traits.h b/media/mojo/mojom/video_encoder_info_mojom_traits.h
index be13d82..7489a24 100644
--- a/media/mojo/mojom/video_encoder_info_mojom_traits.h
+++ b/media/mojo/mojom/video_encoder_info_mojom_traits.h
@@ -75,7 +75,7 @@
       const media::VideoEncoderInfo& video_encoder_info) {
     return video_encoder_info.supports_simulcast;
   }
-  static const media::ScalingSettings& scaling_settings(
+  static const base::Optional<media::ScalingSettings>& scaling_settings(
       const media::VideoEncoderInfo& video_encoder_info) {
     return video_encoder_info.scaling_settings;
   }
diff --git a/media/mojo/services/BUILD.gn b/media/mojo/services/BUILD.gn
index 89d10b7..13acf15 100644
--- a/media/mojo/services/BUILD.gn
+++ b/media/mojo/services/BUILD.gn
@@ -15,8 +15,6 @@
     "gpu_mojo_media_client.h",
     "interface_factory_impl.cc",
     "interface_factory_impl.h",
-    "media_interface_provider.cc",
-    "media_interface_provider.h",
     "media_metrics_provider.cc",
     "media_metrics_provider.h",
     "media_mojo_export.h",
diff --git a/media/mojo/services/android_mojo_media_client.cc b/media/mojo/services/android_mojo_media_client.cc
index aae7227c..fee056b 100644
--- a/media/mojo/services/android_mojo_media_client.cc
+++ b/media/mojo/services/android_mojo_media_client.cc
@@ -34,16 +34,16 @@
 }
 
 std::unique_ptr<CdmFactory> AndroidMojoMediaClient::CreateCdmFactory(
-    service_manager::mojom::InterfaceProvider* host_interfaces) {
-  if (!host_interfaces) {
+    mojom::FrameInterfaceFactory* frame_interfaces) {
+  if (!frame_interfaces) {
     NOTREACHED() << "Host interfaces should be provided when using CDM with "
                  << "AndroidMojoMediaClient";
     return nullptr;
   }
 
   return std::make_unique<AndroidCdmFactory>(
-      base::BindRepeating(&CreateProvisionFetcher, host_interfaces),
-      base::BindRepeating(&CreateMediaDrmStorage, host_interfaces));
+      base::BindRepeating(&CreateProvisionFetcher, frame_interfaces),
+      base::BindRepeating(&CreateMediaDrmStorage, frame_interfaces));
 }
 
 }  // namespace media
diff --git a/media/mojo/services/android_mojo_media_client.h b/media/mojo/services/android_mojo_media_client.h
index 346a708..4ab6dc3 100644
--- a/media/mojo/services/android_mojo_media_client.h
+++ b/media/mojo/services/android_mojo_media_client.h
@@ -23,7 +23,7 @@
       scoped_refptr<base::SingleThreadTaskRunner> task_runner) final;
 
   std::unique_ptr<CdmFactory> CreateCdmFactory(
-      service_manager::mojom::InterfaceProvider* host_interfaces) final;
+      mojom::FrameInterfaceFactory* frame_interfaces) final;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(AndroidMojoMediaClient);
diff --git a/media/mojo/services/android_mojo_util.cc b/media/mojo/services/android_mojo_util.cc
index 6b5a31e..f91415e 100644
--- a/media/mojo/services/android_mojo_util.cc
+++ b/media/mojo/services/android_mojo_util.cc
@@ -6,28 +6,25 @@
 
 #include "media/mojo/services/mojo_media_drm_storage.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
-#include "services/service_manager/public/mojom/interface_provider.mojom.h"
 
 namespace media {
 namespace android_mojo_util {
 
 std::unique_ptr<ProvisionFetcher> CreateProvisionFetcher(
-    service_manager::mojom::InterfaceProvider* host_interfaces) {
-  DCHECK(host_interfaces);
+    media::mojom::FrameInterfaceFactory* frame_interfaces) {
+  DCHECK(frame_interfaces);
   mojo::PendingRemote<mojom::ProvisionFetcher> provision_fetcher;
-  host_interfaces->GetInterface(
-      mojom::ProvisionFetcher::Name_,
-      provision_fetcher.InitWithNewPipeAndPassReceiver().PassPipe());
+  frame_interfaces->CreateProvisionFetcher(
+      provision_fetcher.InitWithNewPipeAndPassReceiver());
   return std::make_unique<MojoProvisionFetcher>(std::move(provision_fetcher));
 }
 
 std::unique_ptr<MediaDrmStorage> CreateMediaDrmStorage(
-    service_manager::mojom::InterfaceProvider* host_interfaces) {
-  DCHECK(host_interfaces);
+    media::mojom::FrameInterfaceFactory* frame_interfaces) {
+  DCHECK(frame_interfaces);
   mojo::PendingRemote<mojom::MediaDrmStorage> media_drm_storage;
-  host_interfaces->GetInterface(
-      mojom::MediaDrmStorage::Name_,
-      media_drm_storage.InitWithNewPipeAndPassReceiver().PassPipe());
+  frame_interfaces->BindEmbedderReceiver(mojo::GenericPendingReceiver(
+      media_drm_storage.InitWithNewPipeAndPassReceiver()));
   return std::make_unique<MojoMediaDrmStorage>(std::move(media_drm_storage));
 }
 
diff --git a/media/mojo/services/android_mojo_util.h b/media/mojo/services/android_mojo_util.h
index 62f1783..3a41f51 100644
--- a/media/mojo/services/android_mojo_util.h
+++ b/media/mojo/services/android_mojo_util.h
@@ -7,25 +7,18 @@
 
 #include <memory>
 
+#include "media/mojo/mojom/frame_interface_factory.mojom.h"
 #include "media/mojo/services/mojo_media_drm_storage.h"
 #include "media/mojo/services/mojo_provision_fetcher.h"
 
-namespace service_manager {
-namespace mojom {
-
-class InterfaceProvider;
-
-}  // namespace mojom
-}  // namespace service_manager
-
 namespace media {
 namespace android_mojo_util {
 
 std::unique_ptr<ProvisionFetcher> CreateProvisionFetcher(
-    service_manager::mojom::InterfaceProvider* host_interfaces);
+    media::mojom::FrameInterfaceFactory* frame_interfaces);
 
 std::unique_ptr<MediaDrmStorage> CreateMediaDrmStorage(
-    service_manager::mojom::InterfaceProvider* host_interfaces);
+    media::mojom::FrameInterfaceFactory* frame_interfaces);
 
 }  // namespace android_mojo_util
 }  // namespace media
diff --git a/media/mojo/services/cdm_service.cc b/media/mojo/services/cdm_service.cc
index 06a61b8..7601bfdf 100644
--- a/media/mojo/services/cdm_service.cc
+++ b/media/mojo/services/cdm_service.cc
@@ -41,9 +41,8 @@
 //     details.
 class CdmFactoryImpl : public DeferredDestroy<mojom::CdmFactory> {
  public:
-  CdmFactoryImpl(
-      CdmService::Client* client,
-      mojo::PendingRemote<service_manager::mojom::InterfaceProvider> interfaces)
+  CdmFactoryImpl(CdmService::Client* client,
+                 mojo::PendingRemote<mojom::FrameInterfaceFactory> interfaces)
       : client_(client), interfaces_(std::move(interfaces)) {
     DVLOG(1) << __func__;
 
@@ -99,7 +98,7 @@
   MojoCdmServiceContext cdm_service_context_;
 
   CdmService::Client* client_;
-  mojo::Remote<service_manager::mojom::InterfaceProvider> interfaces_;
+  mojo::Remote<mojom::FrameInterfaceFactory> interfaces_;
   mojo::UniqueReceiverSet<mojom::ContentDecryptionModule> cdm_receivers_;
   std::unique_ptr<media::CdmFactory> cdm_factory_;
   base::OnceClosure destroy_cb_;
@@ -183,15 +182,14 @@
 
 void CdmService::CreateCdmFactory(
     mojo::PendingReceiver<mojom::CdmFactory> receiver,
-    mojo::PendingRemote<service_manager::mojom::InterfaceProvider>
-        host_interfaces) {
+    mojo::PendingRemote<mojom::FrameInterfaceFactory> frame_interfaces) {
   // Ignore receiver if service has already stopped.
   if (!client_)
     return;
 
   cdm_factory_receivers_.AddReceiver(
       std::make_unique<CdmFactoryImpl>(client_.get(),
-                                       std::move(host_interfaces)),
+                                       std::move(frame_interfaces)),
       std::move(receiver));
 }
 
diff --git a/media/mojo/services/cdm_service.h b/media/mojo/services/cdm_service.h
index 23f683b3..f85cb1a 100644
--- a/media/mojo/services/cdm_service.h
+++ b/media/mojo/services/cdm_service.h
@@ -12,12 +12,12 @@
 #include "media/media_buildflags.h"
 #include "media/mojo/mojom/cdm_service.mojom.h"
 #include "media/mojo/mojom/content_decryption_module.mojom.h"
+#include "media/mojo/mojom/frame_interface_factory.mojom.h"
 #include "media/mojo/services/deferred_destroy_unique_receiver_set.h"
 #include "media/mojo/services/media_mojo_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
-#include "services/service_manager/public/mojom/interface_provider.mojom.h"
 
 #if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION)
 #include "media/cdm/cdm_host_file.h"
@@ -37,11 +37,11 @@
     // be a no-op if the process is already sandboxed.
     virtual void EnsureSandboxed() = 0;
 
-    // Returns the CdmFactory to be used by MojoCdmService. |host_interfaces|
+    // Returns the CdmFactory to be used by MojoCdmService. |frame_interfaces|
     // can be used to request interfaces provided remotely by the host. It may
     // be a nullptr if the host chose not to bind the InterfacePtr.
     virtual std::unique_ptr<CdmFactory> CreateCdmFactory(
-        service_manager::mojom::InterfaceProvider* host_interfaces) = 0;
+        mojom::FrameInterfaceFactory* frame_interfaces) = 0;
 
 #if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION)
     // Gets a list of CDM host file paths and put them in |cdm_host_file_paths|.
@@ -73,8 +73,7 @@
 #endif  // defined(OS_MACOSX)
   void CreateCdmFactory(
       mojo::PendingReceiver<mojom::CdmFactory> receiver,
-      mojo::PendingRemote<service_manager::mojom::InterfaceProvider>
-          host_interfaces) final;
+      mojo::PendingRemote<mojom::FrameInterfaceFactory> frame_interfaces) final;
 
   mojo::Receiver<mojom::CdmService> receiver_;
   std::unique_ptr<Client> client_;
diff --git a/media/mojo/services/cdm_service_unittest.cc b/media/mojo/services/cdm_service_unittest.cc
index 3190e5d..02aec3d2 100644
--- a/media/mojo/services/cdm_service_unittest.cc
+++ b/media/mojo/services/cdm_service_unittest.cc
@@ -12,7 +12,6 @@
 #include "media/cdm/default_cdm_factory.h"
 #include "media/media_buildflags.h"
 #include "media/mojo/services/cdm_service.h"
-#include "media/mojo/services/media_interface_provider.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -45,7 +44,7 @@
   MOCK_METHOD0(EnsureSandboxed, void());
 
   std::unique_ptr<media::CdmFactory> CreateCdmFactory(
-      service_manager::mojom::InterfaceProvider* host_interfaces) override {
+      mojom::FrameInterfaceFactory* frame_interfaces) override {
     return std::make_unique<media::DefaultCdmFactory>();
   }
 
@@ -74,9 +73,8 @@
         base::TimeDelta(), base::BindRepeating(&CdmServiceTest::CdmServiceIdle,
                                                base::Unretained(this)));
 
-    mojo::PendingRemote<service_manager::mojom::InterfaceProvider> interfaces;
-    auto provider = std::make_unique<MediaInterfaceProvider>(
-        interfaces.InitWithNewPipeAndPassReceiver());
+    mojo::PendingRemote<mojom::FrameInterfaceFactory> interfaces;
+    ignore_result(interfaces.InitWithNewPipeAndPassReceiver());
 
     ASSERT_FALSE(cdm_factory_remote_);
     cdm_service_remote_->CreateCdmFactory(
diff --git a/media/mojo/services/gpu_mojo_media_client.cc b/media/mojo/services/gpu_mojo_media_client.cc
index b488e351..f7882160 100644
--- a/media/mojo/services/gpu_mojo_media_client.cc
+++ b/media/mojo/services/gpu_mojo_media_client.cc
@@ -322,7 +322,7 @@
 }
 
 std::unique_ptr<CdmFactory> GpuMojoMediaClient::CreateCdmFactory(
-    service_manager::mojom::InterfaceProvider* interface_provider) {
+    mojom::FrameInterfaceFactory* interface_provider) {
 #if defined(OS_ANDROID)
   return std::make_unique<AndroidCdmFactory>(
       base::BindRepeating(&CreateProvisionFetcher, interface_provider),
diff --git a/media/mojo/services/gpu_mojo_media_client.h b/media/mojo/services/gpu_mojo_media_client.h
index 889fa7f..ead98885 100644
--- a/media/mojo/services/gpu_mojo_media_client.h
+++ b/media/mojo/services/gpu_mojo_media_client.h
@@ -59,7 +59,7 @@
       RequestOverlayInfoCB request_overlay_info_cb,
       const gfx::ColorSpace& target_color_space) final;
   std::unique_ptr<CdmFactory> CreateCdmFactory(
-      service_manager::mojom::InterfaceProvider* interface_provider) final;
+      mojom::FrameInterfaceFactory* interface_provider) final;
 #if BUILDFLAG(ENABLE_CDM_PROXY)
   std::unique_ptr<CdmProxy> CreateCdmProxy(const base::Token& cdm_guid) final;
 #endif  // BUILDFLAG(ENABLE_CDM_PROXY)
diff --git a/media/mojo/services/interface_factory_impl.cc b/media/mojo/services/interface_factory_impl.cc
index 4b07b31..cd0a4ed 100644
--- a/media/mojo/services/interface_factory_impl.cc
+++ b/media/mojo/services/interface_factory_impl.cc
@@ -14,7 +14,6 @@
 #include "media/mojo/mojom/renderer_extensions.mojom.h"
 #include "media/mojo/services/mojo_decryptor_service.h"
 #include "media/mojo/services/mojo_media_client.h"
-#include "services/service_manager/public/mojom/interface_provider.mojom.h"
 
 #if BUILDFLAG(ENABLE_MOJO_AUDIO_DECODER)
 #include "media/mojo/services/mojo_audio_decoder_service.h"
@@ -42,10 +41,9 @@
 namespace media {
 
 InterfaceFactoryImpl::InterfaceFactoryImpl(
-    mojo::PendingRemote<service_manager::mojom::InterfaceProvider>
-        host_interfaces,
+    mojo::PendingRemote<mojom::FrameInterfaceFactory> frame_interfaces,
     MojoMediaClient* mojo_media_client)
-    : host_interfaces_(std::move(host_interfaces)),
+    : frame_interfaces_(std::move(frame_interfaces)),
       mojo_media_client_(mojo_media_client) {
   DVLOG(1) << __func__;
   DCHECK(mojo_media_client_);
@@ -96,7 +94,7 @@
   DVLOG(2) << __func__;
 #if BUILDFLAG(ENABLE_MOJO_RENDERER)
   auto renderer = mojo_media_client_->CreateRenderer(
-      host_interfaces_.get(), base::ThreadTaskRunnerHandle::Get(), &media_log_,
+      frame_interfaces_.get(), base::ThreadTaskRunnerHandle::Get(), &media_log_,
       audio_device_id);
   if (!renderer) {
     DLOG(ERROR) << "Renderer creation failed.";
@@ -126,7 +124,7 @@
     mojo::PendingReceiver<media::mojom::Renderer> receiver) {
   DVLOG(2) << __func__;
   auto renderer = mojo_media_client_->CreateCastRenderer(
-      host_interfaces_.get(), base::ThreadTaskRunnerHandle::Get(), &media_log_,
+      frame_interfaces_.get(), base::ThreadTaskRunnerHandle::Get(), &media_log_,
       overlay_plane_id);
   if (!renderer) {
     DLOG(ERROR) << "Renderer creation failed.";
@@ -294,7 +292,8 @@
 #if BUILDFLAG(ENABLE_MOJO_CDM)
 CdmFactory* InterfaceFactoryImpl::GetCdmFactory() {
   if (!cdm_factory_) {
-    cdm_factory_ = mojo_media_client_->CreateCdmFactory(host_interfaces_.get());
+    cdm_factory_ =
+        mojo_media_client_->CreateCdmFactory(frame_interfaces_.get());
     LOG_IF(ERROR, !cdm_factory_) << "CdmFactory not available.";
   }
   return cdm_factory_.get();
diff --git a/media/mojo/services/interface_factory_impl.h b/media/mojo/services/interface_factory_impl.h
index 79d71cb..439ff6a9 100644
--- a/media/mojo/services/interface_factory_impl.h
+++ b/media/mojo/services/interface_factory_impl.h
@@ -16,6 +16,7 @@
 #include "media/mojo/mojom/audio_decoder.mojom.h"
 #include "media/mojo/mojom/content_decryption_module.mojom.h"
 #include "media/mojo/mojom/decryptor.mojom.h"
+#include "media/mojo/mojom/frame_interface_factory.mojom.h"
 #include "media/mojo/mojom/interface_factory.mojom.h"
 #include "media/mojo/mojom/renderer.mojom.h"
 #include "media/mojo/mojom/video_decoder.mojom.h"
@@ -26,7 +27,6 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/bindings/unique_receiver_set.h"
-#include "services/service_manager/public/mojom/interface_provider.mojom.h"
 
 #if BUILDFLAG(ENABLE_CDM_PROXY)
 #include "media/mojo/mojom/cdm_proxy.mojom.h"
@@ -40,8 +40,7 @@
 class InterfaceFactoryImpl : public DeferredDestroy<mojom::InterfaceFactory> {
  public:
   InterfaceFactoryImpl(
-      mojo::PendingRemote<service_manager::mojom::InterfaceProvider>
-          host_interfaces,
+      mojo::PendingRemote<mojom::FrameInterfaceFactory> frame_interfaces,
       MojoMediaClient* mojo_media_client);
   ~InterfaceFactoryImpl() final;
 
@@ -124,7 +123,7 @@
   mojo::UniqueReceiverSet<mojom::CdmProxy> cdm_proxy_receivers_;
 #endif  // BUILDFLAG(ENABLE_CDM_PROXY)
 
-  mojo::Remote<service_manager::mojom::InterfaceProvider> host_interfaces_;
+  mojo::Remote<mojom::FrameInterfaceFactory> frame_interfaces_;
 
   mojo::UniqueReceiverSet<mojom::Decryptor> decryptor_receivers_;
 
diff --git a/media/mojo/services/media_interface_provider.cc b/media/mojo/services/media_interface_provider.cc
deleted file mode 100644
index 844078f..0000000
--- a/media/mojo/services/media_interface_provider.cc
+++ /dev/null
@@ -1,21 +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 "media/mojo/services/media_interface_provider.h"
-
-namespace media {
-
-MediaInterfaceProvider::MediaInterfaceProvider(
-    mojo::PendingReceiver<service_manager::mojom::InterfaceProvider> receiver)
-    : receiver_(this, std::move(receiver)) {}
-
-MediaInterfaceProvider::~MediaInterfaceProvider() = default;
-
-void MediaInterfaceProvider::GetInterface(
-    const std::string& interface_name,
-    mojo::ScopedMessagePipeHandle handle) {
-  registry_.BindInterface(interface_name, std::move(handle));
-}
-
-}  // namespace media
diff --git a/media/mojo/services/media_interface_provider.h b/media/mojo/services/media_interface_provider.h
deleted file mode 100644
index 26c1e2a..0000000
--- a/media/mojo/services/media_interface_provider.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_MOJO_SERVICES_MEDIA_INTERFACE_PROVIDER_H_
-#define MEDIA_MOJO_SERVICES_MEDIA_INTERFACE_PROVIDER_H_
-
-#include "media/mojo/services/media_mojo_export.h"
-#include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-#include "services/service_manager/public/cpp/binder_registry.h"
-#include "services/service_manager/public/mojom/interface_provider.mojom.h"
-
-namespace media {
-
-class MEDIA_MOJO_EXPORT MediaInterfaceProvider
-    : public service_manager::mojom::InterfaceProvider {
- public:
-  explicit MediaInterfaceProvider(
-      mojo::PendingReceiver<service_manager::mojom::InterfaceProvider>
-          receiver);
-  ~MediaInterfaceProvider() override;
-
-  service_manager::BinderRegistry* registry() { return &registry_; }
-
- private:
-  // service_manager::mojom::InterfaceProvider:
-  void GetInterface(const std::string& interface_name,
-                    mojo::ScopedMessagePipeHandle handle) override;
-
-  service_manager::BinderRegistry registry_;
-
-  mojo::Receiver<service_manager::mojom::InterfaceProvider> receiver_;
-
-  DISALLOW_COPY_AND_ASSIGN(MediaInterfaceProvider);
-};
-
-}  // namespace media
-
-#endif  // MEDIA_MOJO_SERVICES_MEDIA_BINDER_REGISTRY_H_
diff --git a/media/mojo/services/media_service.cc b/media/mojo/services/media_service.cc
index c669262a..e00bed6 100644
--- a/media/mojo/services/media_service.cc
+++ b/media/mojo/services/media_service.cc
@@ -25,14 +25,13 @@
 
 void MediaService::CreateInterfaceFactory(
     mojo::PendingReceiver<mojom::InterfaceFactory> receiver,
-    mojo::PendingRemote<service_manager::mojom::InterfaceProvider>
-        host_interfaces) {
+    mojo::PendingRemote<mojom::FrameInterfaceFactory> frame_interfaces) {
   // Ignore request if service has already stopped.
   if (!mojo_media_client_)
     return;
 
   interface_factory_receivers_.Add(
-      std::make_unique<InterfaceFactoryImpl>(std::move(host_interfaces),
+      std::make_unique<InterfaceFactoryImpl>(std::move(frame_interfaces),
                                              mojo_media_client_.get()),
       std::move(receiver));
 }
diff --git a/media/mojo/services/media_service.h b/media/mojo/services/media_service.h
index 9656a8a1..01f72ae 100644
--- a/media/mojo/services/media_service.h
+++ b/media/mojo/services/media_service.h
@@ -9,6 +9,7 @@
 
 #include "base/macros.h"
 #include "build/build_config.h"
+#include "media/mojo/mojom/frame_interface_factory.mojom.h"
 #include "media/mojo/mojom/interface_factory.mojom.h"
 #include "media/mojo/mojom/media_service.mojom.h"
 #include "media/mojo/services/media_mojo_export.h"
@@ -16,7 +17,6 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/unique_receiver_set.h"
-#include "services/service_manager/public/mojom/interface_provider.mojom.h"
 
 namespace media {
 
@@ -34,8 +34,7 @@
   // mojom::MediaService implementation:
   void CreateInterfaceFactory(
       mojo::PendingReceiver<mojom::InterfaceFactory> receiver,
-      mojo::PendingRemote<service_manager::mojom::InterfaceProvider>
-          host_interfaces) final;
+      mojo::PendingRemote<mojom::FrameInterfaceFactory> frame_interfaces) final;
 
   mojo::Receiver<mojom::MediaService> receiver_;
 
diff --git a/media/mojo/services/media_service_unittest.cc b/media/mojo/services/media_service_unittest.cc
index a0852d6b6..cd5e99b 100644
--- a/media/mojo/services/media_service_unittest.cc
+++ b/media/mojo/services/media_service_unittest.cc
@@ -27,7 +27,6 @@
 #include "media/mojo/mojom/interface_factory.mojom.h"
 #include "media/mojo/mojom/media_service.mojom.h"
 #include "media/mojo/mojom/renderer.mojom.h"
-#include "media/mojo/services/media_interface_provider.h"
 #include "media/mojo/services/media_service_factory.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
@@ -137,10 +136,8 @@
   ~MediaServiceTest() override = default;
 
   void SetUp() override {
-    mojo::PendingRemote<service_manager::mojom::InterfaceProvider>
-        host_interfaces;
-    auto provider = std::make_unique<MediaInterfaceProvider>(
-        host_interfaces.InitWithNewPipeAndPassReceiver());
+    mojo::PendingRemote<mojom::FrameInterfaceFactory> frame_interfaces;
+    ignore_result(frame_interfaces.InitWithNewPipeAndPassReceiver());
 
     media_service_impl_ = CreateMediaServiceForTesting(
         media_service_.BindNewPipeAndPassReceiver());
@@ -150,7 +147,7 @@
                             base::Unretained(this)));
     media_service_->CreateInterfaceFactory(
         interface_factory_.BindNewPipeAndPassReceiver(),
-        std::move(host_interfaces));
+        std::move(frame_interfaces));
   }
 
   MOCK_METHOD3(OnCdmInitialized,
diff --git a/media/mojo/services/mojo_cdm_helper.cc b/media/mojo/services/mojo_cdm_helper.cc
index ae74069..0bc620f 100644
--- a/media/mojo/services/mojo_cdm_helper.cc
+++ b/media/mojo/services/mojo_cdm_helper.cc
@@ -11,13 +11,11 @@
 #include "media/mojo/services/mojo_cdm_file_io.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
-#include "services/service_manager/public/cpp/connect.h"
 
 namespace media {
 
-MojoCdmHelper::MojoCdmHelper(
-    service_manager::mojom::InterfaceProvider* interface_provider)
-    : interface_provider_(interface_provider) {}
+MojoCdmHelper::MojoCdmHelper(mojom::FrameInterfaceFactory* frame_interfaces)
+    : frame_interfaces_(frame_interfaces) {}
 
 MojoCdmHelper::~MojoCdmHelper() = default;
 
@@ -48,8 +46,8 @@
   }
 
   mojo::PendingRemote<mojom::CdmProxy> cdm_proxy_remote;
-  service_manager::GetInterface<mojom::CdmProxy>(
-      interface_provider_, cdm_proxy_remote.InitWithNewPipeAndPassReceiver());
+  frame_interfaces_->CreateCdmProxy(
+      cdm_proxy_remote.InitWithNewPipeAndPassReceiver());
   cdm_proxy_ =
       std::make_unique<MojoCdmProxy>(std::move(cdm_proxy_remote), client);
   return cdm_proxy_.get();
@@ -118,8 +116,8 @@
 
 void MojoCdmHelper::ConnectToCdmStorage() {
   if (!cdm_storage_remote_) {
-    service_manager::GetInterface<mojom::CdmStorage>(
-        interface_provider_, cdm_storage_remote_.BindNewPipeAndPassReceiver());
+    frame_interfaces_->CreateCdmStorage(
+        cdm_storage_remote_.BindNewPipeAndPassReceiver());
   }
 }
 
@@ -131,16 +129,15 @@
 
 void MojoCdmHelper::ConnectToOutputProtection() {
   if (!output_protection_) {
-    service_manager::GetInterface<mojom::OutputProtection>(
-        interface_provider_, output_protection_.BindNewPipeAndPassReceiver());
+    frame_interfaces_->BindEmbedderReceiver(mojo::GenericPendingReceiver(
+        output_protection_.BindNewPipeAndPassReceiver()));
   }
 }
 
 void MojoCdmHelper::ConnectToPlatformVerification() {
   if (!platform_verification_) {
-    interface_provider_->GetInterface(
-        mojom::PlatformVerification::Name_,
-        platform_verification_.BindNewPipeAndPassReceiver().PassPipe());
+    frame_interfaces_->BindEmbedderReceiver(mojo::GenericPendingReceiver(
+        platform_verification_.BindNewPipeAndPassReceiver()));
   }
 }
 
diff --git a/media/mojo/services/mojo_cdm_helper.h b/media/mojo/services/mojo_cdm_helper.h
index 4a87502..cc07baf 100644
--- a/media/mojo/services/mojo_cdm_helper.h
+++ b/media/mojo/services/mojo_cdm_helper.h
@@ -14,6 +14,7 @@
 #include "media/cdm/cdm_auxiliary_helper.h"
 #include "media/media_buildflags.h"
 #include "media/mojo/mojom/cdm_storage.mojom.h"
+#include "media/mojo/mojom/frame_interface_factory.mojom.h"
 #include "media/mojo/mojom/output_protection.mojom.h"
 #include "media/mojo/mojom/platform_verification.mojom.h"
 #include "media/mojo/services/media_mojo_export.h"
@@ -25,12 +26,6 @@
 #include "media/mojo/services/mojo_cdm_proxy.h"
 #endif
 
-namespace service_manager {
-namespace mojom {
-class InterfaceProvider;
-}
-}  // namespace service_manager
-
 namespace media {
 
 // Helper class that connects the CDM to various auxiliary services. All
@@ -39,8 +34,7 @@
 class MEDIA_MOJO_EXPORT MojoCdmHelper final : public CdmAuxiliaryHelper,
                                               public MojoCdmFileIO::Delegate {
  public:
-  explicit MojoCdmHelper(
-      service_manager::mojom::InterfaceProvider* interface_provider);
+  explicit MojoCdmHelper(mojom::FrameInterfaceFactory* frame_interfaces);
   ~MojoCdmHelper() final;
 
   // CdmAuxiliaryHelper implementation.
@@ -72,7 +66,7 @@
   void ConnectToPlatformVerification();
 
   // Provides interfaces when needed.
-  service_manager::mojom::InterfaceProvider* interface_provider_;
+  mojom::FrameInterfaceFactory* frame_interfaces_;
 
   // Connections to the additional services. For the mojom classes, if a
   // connection error occurs, we will not be able to reconnect to the
diff --git a/media/mojo/services/mojo_cdm_helper_unittest.cc b/media/mojo/services/mojo_cdm_helper_unittest.cc
index fcdec73..db3e2df 100644
--- a/media/mojo/services/mojo_cdm_helper_unittest.cc
+++ b/media/mojo/services/mojo_cdm_helper_unittest.cc
@@ -13,8 +13,6 @@
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
-#include "services/service_manager/public/cpp/binder_registry.h"
-#include "services/service_manager/public/mojom/interface_provider.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -66,36 +64,30 @@
   mojo::AssociatedReceiver<mojom::CdmFile> client_receiver_{&cdm_file_};
 };
 
-void CreateCdmStorage(mojo::PendingReceiver<mojom::CdmStorage> receiver) {
-  mojo::MakeSelfOwnedReceiver(std::make_unique<MockCdmStorage>(),
-                              std::move(receiver));
-}
-
-class TestInterfaceProvider : public service_manager::mojom::InterfaceProvider {
+class TestFrameInterfaceFactory : public mojom::FrameInterfaceFactory {
  public:
-  TestInterfaceProvider() {
-    registry_.AddInterface(base::Bind(&CreateCdmStorage));
+  void CreateProvisionFetcher(
+      mojo::PendingReceiver<mojom::ProvisionFetcher>) override {}
+  void CreateCdmStorage(
+      mojo::PendingReceiver<mojom::CdmStorage> receiver) override {
+    mojo::MakeSelfOwnedReceiver(std::make_unique<MockCdmStorage>(),
+                                std::move(receiver));
   }
-  ~TestInterfaceProvider() override = default;
-
-  void GetInterface(const std::string& interface_name,
-                    mojo::ScopedMessagePipeHandle handle) override {
-    registry_.BindInterface(interface_name, std::move(handle));
-  }
-
- private:
-  service_manager::BinderRegistry registry_;
+#if BUILDFLAG(ENABLE_CDM_PROXY)
+  void CreateCdmProxy(mojo::PendingReceiver<mojom::CdmProxy>) override {}
+#endif
+  void BindEmbedderReceiver(mojo::GenericPendingReceiver) override {}
 };
 
 }  // namespace
 
 class MojoCdmHelperTest : public testing::Test {
  protected:
-  MojoCdmHelperTest() : helper_(&test_interface_provider_) {}
+  MojoCdmHelperTest() : helper_(&frame_interfaces_) {}
   ~MojoCdmHelperTest() override = default;
 
   base::test::TaskEnvironment task_environment_;
-  TestInterfaceProvider test_interface_provider_;
+  TestFrameInterfaceFactory frame_interfaces_;
   MockFileIOClient file_io_client_;
   MojoCdmHelper helper_;
 };
diff --git a/media/mojo/services/mojo_media_client.cc b/media/mojo/services/mojo_media_client.cc
index 4b4fca7..bb5729b1 100644
--- a/media/mojo/services/mojo_media_client.cc
+++ b/media/mojo/services/mojo_media_client.cc
@@ -44,7 +44,7 @@
 }
 
 std::unique_ptr<Renderer> MojoMediaClient::CreateRenderer(
-    service_manager::mojom::InterfaceProvider* host_interfaces,
+    mojom::FrameInterfaceFactory* frame_interfaces,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
     MediaLog* media_log,
     const std::string& audio_device_id) {
@@ -53,7 +53,7 @@
 
 #if BUILDFLAG(ENABLE_CAST_RENDERER)
 std::unique_ptr<Renderer> MojoMediaClient::CreateCastRenderer(
-    service_manager::mojom::InterfaceProvider* host_interfaces,
+    mojom::FrameInterfaceFactory* frame_interfaces,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
     MediaLog* media_log,
     const base::UnguessableToken& overlay_plane_id) {
@@ -62,7 +62,7 @@
 #endif  // BUILDFLAG(ENABLE_CAST_RENDERER)
 
 std::unique_ptr<CdmFactory> MojoMediaClient::CreateCdmFactory(
-    service_manager::mojom::InterfaceProvider* host_interfaces) {
+    mojom::FrameInterfaceFactory* frame_interfaces) {
   return nullptr;
 }
 
diff --git a/media/mojo/services/mojo_media_client.h b/media/mojo/services/mojo_media_client.h
index 03e62e3f..6dbbd2d 100644
--- a/media/mojo/services/mojo_media_client.h
+++ b/media/mojo/services/mojo_media_client.h
@@ -15,6 +15,7 @@
 #include "media/base/overlay_info.h"
 #include "media/media_buildflags.h"
 #include "media/mojo/buildflags.h"
+#include "media/mojo/mojom/frame_interface_factory.mojom.h"
 #include "media/mojo/mojom/video_decoder.mojom.h"
 #include "media/mojo/services/media_mojo_export.h"
 #include "media/video/supported_video_decoder_config.h"
@@ -28,12 +29,6 @@
 class ColorSpace;
 }  // namespace gfx
 
-namespace service_manager {
-namespace mojom {
-class InterfaceProvider;
-}  // namespace mojom
-}  // namespace service_manager
-
 namespace media {
 
 class AudioDecoder;
@@ -76,7 +71,7 @@
   // TODO(hubbe): Find out whether we should pass in |target_color_space| here.
   // TODO(guohuideng): Merge this function into CreateCastRenderer.
   virtual std::unique_ptr<Renderer> CreateRenderer(
-      service_manager::mojom::InterfaceProvider* host_interfaces,
+      mojom::FrameInterfaceFactory* frame_interfaces,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner,
       MediaLog* media_log,
       const std::string& audio_device_id);
@@ -88,17 +83,17 @@
   // associtated with.
   // Chromecast also uses CreateRenderer to create "audio only" renderers.
   virtual std::unique_ptr<Renderer> CreateCastRenderer(
-      service_manager::mojom::InterfaceProvider* host_interfaces,
+      mojom::FrameInterfaceFactory* frame_interfaces,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner,
       MediaLog* media_log,
       const base::UnguessableToken& overlay_plane_id);
 #endif  // BUILDFLAG(ENABLE_CAST_RENDERER)
 
-  // Returns the CdmFactory to be used by MojoCdmService. |host_interfaces| can
+  // Returns the CdmFactory to be used by MojoCdmService. |frame_interfaces| can
   // be used to request interfaces provided remotely by the host. It may be a
   // nullptr if the host chose not to bind the InterfacePtr.
   virtual std::unique_ptr<CdmFactory> CreateCdmFactory(
-      service_manager::mojom::InterfaceProvider* host_interfaces);
+      mojom::FrameInterfaceFactory* frame_interfaces);
 
 #if BUILDFLAG(ENABLE_CDM_PROXY)
   // Creates a CdmProxy that proxies part of CDM functionalities to a different
diff --git a/media/mojo/services/test_mojo_media_client.cc b/media/mojo/services/test_mojo_media_client.cc
index 5b3d626..32a6fa86 100644
--- a/media/mojo/services/test_mojo_media_client.cc
+++ b/media/mojo/services/test_mojo_media_client.cc
@@ -56,7 +56,7 @@
 }
 
 std::unique_ptr<Renderer> TestMojoMediaClient::CreateRenderer(
-    service_manager::mojom::InterfaceProvider* host_interfaces,
+    mojom::FrameInterfaceFactory* frame_interfaces,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
     MediaLog* media_log,
     const std::string& /* audio_device_id */) {
@@ -98,16 +98,17 @@
 
 #if BUILDFLAG(ENABLE_CAST_RENDERER)
 std::unique_ptr<Renderer> TestMojoMediaClient::CreateCastRenderer(
-    service_manager::mojom::InterfaceProvider* host_interfaces,
+    mojom::FrameInterfaceFactory* frame_interfaces,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
     MediaLog* media_log,
     const base::UnguessableToken& /* overlay_plane_id */) {
-  return CreateRenderer(host_interfaces, task_runner, media_log, std::string());
+  return CreateRenderer(frame_interfaces, task_runner, media_log,
+                        std::string());
 }
 #endif  // BUILDFLAG(ENABLE_CAST_RENDERER)
 
 std::unique_ptr<CdmFactory> TestMojoMediaClient::CreateCdmFactory(
-    service_manager::mojom::InterfaceProvider* /* host_interfaces */) {
+    mojom::FrameInterfaceFactory* /* frame_interfaces */) {
   DVLOG(1) << __func__;
   return std::make_unique<DefaultCdmFactory>();
 }
diff --git a/media/mojo/services/test_mojo_media_client.h b/media/mojo/services/test_mojo_media_client.h
index d72fedd..a626c13 100644
--- a/media/mojo/services/test_mojo_media_client.h
+++ b/media/mojo/services/test_mojo_media_client.h
@@ -30,19 +30,19 @@
   // MojoMediaClient implementation.
   void Initialize() final;
   std::unique_ptr<Renderer> CreateRenderer(
-      service_manager::mojom::InterfaceProvider* host_interfaces,
+      mojom::FrameInterfaceFactory* frame_interfaces,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner,
       MediaLog* media_log,
       const std::string& audio_device_id) final;
 #if BUILDFLAG(ENABLE_CAST_RENDERER)
   std::unique_ptr<Renderer> CreateCastRenderer(
-      service_manager::mojom::InterfaceProvider* host_interfaces,
+      mojom::FrameInterfaceFactory* frame_interfaces,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner,
       MediaLog* media_log,
       const base::UnguessableToken& overlay_plane_id) final;
 #endif  // BUILDFLAG(ENABLE_CAST_RENDERER)
   std::unique_ptr<CdmFactory> CreateCdmFactory(
-      service_manager::mojom::InterfaceProvider* /* host_interfaces */) final;
+      mojom::FrameInterfaceFactory* /* frame_interfaces */) final;
 #if BUILDFLAG(ENABLE_CDM_PROXY)
   std::unique_ptr<CdmProxy> CreateCdmProxy(const base::Token& cdm_guid) final;
 #endif  // BUILDFLAG(ENABLE_CDM_PROXY)
diff --git a/media/video/video_encoder_info.h b/media/video/video_encoder_info.h
index 97ca2cd8..0258afb 100644
--- a/media/video/video_encoder_info.h
+++ b/media/video/video_encoder_info.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/optional.h"
 #include "media/base/media_export.h"
 #include "ui/gfx/geometry/size.h"
 
@@ -23,8 +24,10 @@
   ScalingSettings(const ScalingSettings&);
   ~ScalingSettings();
 
-  int min_qp = 4;
-  int max_qp = 157;
+  // Quantization Parameter in ScalingSettings are codec specific.
+  // The range of qp is 0-51 (H264), 0-127 (VP8) and 0-255 (VP9 and AV1).
+  int min_qp = 0;
+  int max_qp = 255;
 };
 
 struct MEDIA_EXPORT ResolutionBitrateLimit {
@@ -56,7 +59,7 @@
   bool is_hardware_accelerated = true;
   bool supports_simulcast = false;
 
-  ScalingSettings scaling_settings;
+  base::Optional<ScalingSettings> scaling_settings;
   std::vector<uint8_t> fps_allocation[kMaxSpatialLayers];
   std::vector<ResolutionBitrateLimit> resolution_bitrate_limits;
 };
diff --git a/net/cookies/canonical_cookie.cc b/net/cookies/canonical_cookie.cc
index fb17243..2b18ca8 100644
--- a/net/cookies/canonical_cookie.cc
+++ b/net/cookies/canonical_cookie.cc
@@ -122,13 +122,13 @@
     CookieOptions::SameSiteCookieContext same_site_context,
     CanonicalCookie::CookieInclusionStatus* status) {
   if (samesite == CookieSameSite::UNSPECIFIED &&
-      same_site_context.context <
+      same_site_context.GetContextForCookieInclusion() <
           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) {
     status->AddWarningReason(CanonicalCookie::CookieInclusionStatus::
                                  WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT);
   }
   if (effective_samesite == CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE &&
-      same_site_context.context ==
+      same_site_context.GetContextForCookieInclusion() ==
           CookieOptions::SameSiteCookieContext::ContextType::
               SAME_SITE_LAX_METHOD_UNSAFE) {
     // This warning is more specific so remove the previous, more general,
@@ -158,9 +158,9 @@
     const CookieOptions::SameSiteCookieContext& context,
     const CookieEffectiveSameSite effective_same_site) {
   bool correct_context =
-      context.cross_schemeness !=
+      context.cross_schemeness() !=
           CookieOptions::SameSiteCookieContext::CrossSchemeness::NONE &&
-      context.context !=
+      context.context() !=
           CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE;
 
   bool correct_effective_same_site =
@@ -498,24 +498,24 @@
   }
   UMA_HISTOGRAM_ENUMERATION(
       "Cookie.RequestSameSiteContext",
-      options.same_site_cookie_context().context,
+      options.same_site_cookie_context().GetContextForCookieInclusion(),
       CookieOptions::SameSiteCookieContext::ContextType::COUNT);
 
   switch (effective_same_site) {
     case CookieEffectiveSameSite::STRICT_MODE:
-      if (options.same_site_cookie_context().context <
+      if (options.same_site_cookie_context().GetContextForCookieInclusion() <
           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT) {
         status.AddExclusionReason(
             CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT);
       }
       break;
     case CookieEffectiveSameSite::LAX_MODE:
-      if (options.same_site_cookie_context().context <
+      if (options.same_site_cookie_context().GetContextForCookieInclusion() <
           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) {
         // Log metrics for a cookie that would have been included under the
         // "Lax-allow-unsafe" intervention, had it been new enough.
         if (SameSite() == CookieSameSite::UNSPECIFIED &&
-            options.same_site_cookie_context().context ==
+            options.same_site_cookie_context().GetContextForCookieInclusion() ==
                 CookieOptions::SameSiteCookieContext::ContextType::
                     SAME_SITE_LAX_METHOD_UNSAFE) {
           UMA_HISTOGRAM_CUSTOM_TIMES(
@@ -533,13 +533,14 @@
     // TODO(crbug.com/990439): Add a browsertest for this behavior.
     case CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE:
       DCHECK(SameSite() == CookieSameSite::UNSPECIFIED);
-      if (options.same_site_cookie_context().context <
+      if (options.same_site_cookie_context().GetContextForCookieInclusion() <
           CookieOptions::SameSiteCookieContext::ContextType::
               SAME_SITE_LAX_METHOD_UNSAFE) {
         // TODO(chlily): Do we need a separate CookieInclusionStatus for this?
         status.AddExclusionReason(
             CookieInclusionStatus::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX);
-      } else if (options.same_site_cookie_context().context ==
+      } else if (options.same_site_cookie_context()
+                     .GetContextForCookieInclusion() ==
                  CookieOptions::SameSiteCookieContext::ContextType::
                      SAME_SITE_LAX_METHOD_UNSAFE) {
         // Log metrics for cookies that activate the "Lax-allow-unsafe"
@@ -633,7 +634,7 @@
       // This intentionally checks for `< SAME_SITE_LAX`, as we allow
       // `SameSite=Strict` cookies to be set for top-level navigations that
       // qualify for receipt of `SameSite=Lax` cookies.
-      if (options.same_site_cookie_context().context <
+      if (options.same_site_cookie_context().GetContextForCookieInclusion() <
           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) {
         DVLOG(net::cookie_util::kVlogSetCookies)
             << "Trying to set a `SameSite=Strict` cookie from a "
@@ -644,7 +645,7 @@
       break;
     case CookieEffectiveSameSite::LAX_MODE:
     case CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE:
-      if (options.same_site_cookie_context().context <
+      if (options.same_site_cookie_context().GetContextForCookieInclusion() <
           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) {
         if (SameSite() == CookieSameSite::UNSPECIFIED) {
           DVLOG(net::cookie_util::kVlogSetCookies)
@@ -779,9 +780,9 @@
 void net::CanonicalCookie::AddSameSiteCrossSchemeWarning(
     CookieInclusionStatus* status,
     CookieOptions::SameSiteCookieContext same_site_context) const {
-  if (same_site_context.cross_schemeness ==
+  if (same_site_context.cross_schemeness() ==
       CookieOptions::SameSiteCookieContext::CrossSchemeness::INSECURE_SECURE) {
-    switch (same_site_context.context) {
+    switch (same_site_context.context()) {
       case CookieOptions::SameSiteCookieContext::ContextType::
           SAME_SITE_LAX_METHOD_UNSAFE:
         status->AddWarningReason(
@@ -800,10 +801,10 @@
       default:
         break;
     }
-  } else if (same_site_context.cross_schemeness ==
+  } else if (same_site_context.cross_schemeness() ==
              CookieOptions::SameSiteCookieContext::CrossSchemeness::
                  SECURE_INSECURE) {
-    switch (same_site_context.context) {
+    switch (same_site_context.context()) {
       case CookieOptions::SameSiteCookieContext::ContextType::
           SAME_SITE_LAX_METHOD_UNSAFE:
         status->AddWarningReason(
diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
index 783485c..292f391 100644
--- a/net/cookies/cookie_monster.cc
+++ b/net/cookies/cookie_monster.cc
@@ -303,7 +303,8 @@
     const CookieOptions& options,
     CookieOptions::SameSiteCookieContext::ContextType same_site_requirement) {
   return !options.exclude_httponly() &&
-         options.same_site_cookie_context().context >= same_site_requirement;
+         options.same_site_cookie_context().GetContextForCookieInclusion() >=
+             same_site_requirement;
 }
 
 }  // namespace
diff --git a/net/cookies/cookie_options.cc b/net/cookies/cookie_options.cc
index 252de95..9b285133 100644
--- a/net/cookies/cookie_options.cc
+++ b/net/cookies/cookie_options.cc
@@ -13,13 +13,29 @@
   return SameSiteCookieContext(ContextType::SAME_SITE_STRICT);
 }
 
+CookieOptions::SameSiteCookieContext::ContextType
+CookieOptions::SameSiteCookieContext::GetContextForCookieInclusion() const {
+  return context_;
+}
+
 int64_t CookieOptions::SameSiteCookieContext::ConvertToMetricsValue() const {
-  if (cross_schemeness == CrossSchemeness::INSECURE_SECURE) {
-    return static_cast<int64_t>(context) | kToSecureMask;
-  } else if (cross_schemeness == CrossSchemeness::SECURE_INSECURE) {
-    return static_cast<int64_t>(context) | kToInsecureMask;
+  if (cross_schemeness_ == CrossSchemeness::INSECURE_SECURE) {
+    return static_cast<int64_t>(context_) | kToSecureMask;
+  } else if (cross_schemeness_ == CrossSchemeness::SECURE_INSECURE) {
+    return static_cast<int64_t>(context_) | kToInsecureMask;
   }
-  return static_cast<int64_t>(context);
+  return static_cast<int64_t>(context_);
+}
+
+bool operator==(const CookieOptions::SameSiteCookieContext& lhs,
+                const CookieOptions::SameSiteCookieContext& rhs) {
+  return std::tie(lhs.context_, lhs.cross_schemeness_) ==
+         std::tie(rhs.context_, rhs.cross_schemeness_);
+}
+
+bool operator!=(const CookieOptions::SameSiteCookieContext& lhs,
+                const CookieOptions::SameSiteCookieContext& rhs) {
+  return !(lhs == rhs);
 }
 
 // Keep default values in sync with content/public/common/cookie_manager.mojom.
@@ -39,15 +55,4 @@
   return options;
 }
 
-bool operator==(const CookieOptions::SameSiteCookieContext& lhs,
-                const CookieOptions::SameSiteCookieContext& rhs) {
-  return std::tie(lhs.context, lhs.cross_schemeness) ==
-         std::tie(rhs.context, rhs.cross_schemeness);
-}
-
-bool operator!=(const CookieOptions::SameSiteCookieContext& lhs,
-                const CookieOptions::SameSiteCookieContext& rhs) {
-  return !(lhs == rhs);
-}
-
 }  // namespace net
diff --git a/net/cookies/cookie_options.h b/net/cookies/cookie_options.h
index db0c434..4fb1248 100644
--- a/net/cookies/cookie_options.h
+++ b/net/cookies/cookie_options.h
@@ -46,12 +46,17 @@
     explicit SameSiteCookieContext(
         ContextType same_site_context,
         CrossSchemeness cross_schemeness = CrossSchemeness::NONE)
-        : context(same_site_context), cross_schemeness(cross_schemeness) {}
+        : context_(same_site_context), cross_schemeness_(cross_schemeness) {}
 
     // Convenience method which returns a SameSiteCookieContext with the most
     // inclusive context. This allows access to all SameSite cookies.
     static SameSiteCookieContext MakeInclusive();
 
+    // Returns the context for determining SameSite cookie inclusion.
+    // TODO(https://crbug.com/1030938): When schemeful_context is
+    // implemented choose to return it based on feature::kSchemefulSameSite.
+    ContextType GetContextForCookieInclusion() const;
+
     // The following functions are for conversion to the previous style of
     // SameSiteCookieContext for metrics usage. This may be removed when the
     // metrics using them are also removed.
@@ -64,9 +69,23 @@
     }
     int64_t ConvertToMetricsValue() const;
 
-    ContextType context;
+    // If you're just trying to determine if a cookie is accessible you likely
+    // want to use GetContextForCookieInclusion() which will return the correct
+    // context regardless the status of same-site features.
+    ContextType context() const { return context_; }
+    void set_context(ContextType context) { context_ = context; }
 
-    CrossSchemeness cross_schemeness;
+    CrossSchemeness cross_schemeness() const { return cross_schemeness_; }
+    void set_cross_schemeness(CrossSchemeness cross_schemeness) {
+      cross_schemeness_ = cross_schemeness;
+    }
+
+    NET_EXPORT friend bool operator==(
+        const CookieOptions::SameSiteCookieContext& lhs,
+        const CookieOptions::SameSiteCookieContext& rhs);
+    NET_EXPORT friend bool operator!=(
+        const CookieOptions::SameSiteCookieContext& lhs,
+        const CookieOptions::SameSiteCookieContext& rhs);
 
    private:
     // The following variables are for conversion to the previous style of
@@ -76,6 +95,10 @@
     static const int kToSecureMask = 1 << 5;
     // Mask indicating secure site-for-cookies and insecure request/response.
     static const int kToInsecureMask = kToSecureMask << 1;
+
+    ContextType context_;
+
+    CrossSchemeness cross_schemeness_;
   };
 
   // Creates a CookieOptions object which:
@@ -131,12 +154,6 @@
   bool return_excluded_cookies_;
 };
 
-NET_EXPORT bool operator==(const CookieOptions::SameSiteCookieContext& lhs,
-                           const CookieOptions::SameSiteCookieContext& rhs);
-
-NET_EXPORT bool operator!=(const CookieOptions::SameSiteCookieContext& lhs,
-                           const CookieOptions::SameSiteCookieContext& rhs);
-
 }  // namespace net
 
 #endif  // NET_COOKIES_COOKIE_OPTIONS_H_
diff --git a/net/cookies/cookie_util.cc b/net/cookies/cookie_util.cc
index ed58360..70d3605 100644
--- a/net/cookies/cookie_util.cc
+++ b/net/cookies/cookie_util.cc
@@ -106,14 +106,15 @@
     // IsFirstParty().
     if (!initiator ||
         SiteForCookies::FromOrigin(initiator.value()).IsFirstParty(url)) {
-      same_site_type.context =
-          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT;
+      same_site_type.set_context(
+          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT);
     } else {
-      same_site_type.context =
-          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX;
+      same_site_type.set_context(
+          CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX);
     }
   }
-  same_site_type.cross_schemeness = ComputeSchemeChange(url, site_for_cookies);
+  same_site_type.set_cross_schemeness(
+      ComputeSchemeChange(url, site_for_cookies));
   return same_site_type;
 }
 
@@ -449,10 +450,10 @@
   CookieOptions::SameSiteCookieContext same_site_context;
 
   if (attach_same_site_cookies) {
-    same_site_context.context =
-        CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT;
-    same_site_context.cross_schemeness =
-        ComputeSchemeChange(url, site_for_cookies);
+    same_site_context.set_context(
+        CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT);
+    same_site_context.set_cross_schemeness(
+        ComputeSchemeChange(url, site_for_cookies));
     return same_site_context;
   }
 
@@ -460,11 +461,11 @@
 
   // If the method is safe, the context is Lax. Otherwise, make a note that
   // the method is unsafe.
-  if (same_site_context.context ==
+  if (same_site_context.context() ==
           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX &&
       !net::HttpUtil::IsMethodSafe(http_method)) {
-    same_site_context.context = CookieOptions::SameSiteCookieContext::
-        ContextType::SAME_SITE_LAX_METHOD_UNSAFE;
+    same_site_context.set_context(CookieOptions::SameSiteCookieContext::
+                                      ContextType::SAME_SITE_LAX_METHOD_UNSAFE);
   }
 
   return same_site_context;
@@ -478,8 +479,8 @@
   if (attach_same_site_cookies) {
     CookieOptions::SameSiteCookieContext same_site_context(
         CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT);
-    same_site_context.cross_schemeness =
-        ComputeSchemeChange(url, site_for_cookies);
+    same_site_context.set_cross_schemeness(
+        ComputeSchemeChange(url, site_for_cookies));
     return same_site_context;
   }
   return ComputeSameSiteContext(url, site_for_cookies, initiator);
@@ -494,14 +495,14 @@
   // |initiator| is here in case it'll be decided to ignore |site_for_cookies|
   // for entirely browser-side requests (see https://crbug.com/958335).
   if (attach_same_site_cookies || site_for_cookies.IsFirstParty(url)) {
-    same_site_context.context =
-        CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX;
+    same_site_context.set_context(
+        CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX);
   } else {
-    same_site_context.context =
-        CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE;
+    same_site_context.set_context(
+        CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE);
   }
-  same_site_context.cross_schemeness =
-      ComputeSchemeChange(url, site_for_cookies);
+  same_site_context.set_cross_schemeness(
+      ComputeSchemeChange(url, site_for_cookies));
   return same_site_context;
 }
 
@@ -511,14 +512,14 @@
     bool attach_same_site_cookies) {
   CookieOptions::SameSiteCookieContext same_site_context;
   if (attach_same_site_cookies || site_for_cookies.IsFirstParty(url)) {
-    same_site_context.context =
-        CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX;
+    same_site_context.set_context(
+        CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX);
   } else {
-    same_site_context.context =
-        CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE;
+    same_site_context.set_context(
+        CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE);
   }
-  same_site_context.cross_schemeness =
-      ComputeSchemeChange(url, site_for_cookies);
+  same_site_context.set_cross_schemeness(
+      ComputeSchemeChange(url, site_for_cookies));
   return same_site_context;
 }
 
@@ -530,14 +531,14 @@
   // If the URL is same-site as site_for_cookies it's same-site as all frames
   // in the tree from the initiator frame up --- including the initiator frame.
   if (attach_same_site_cookies || site_for_cookies.IsFirstParty(url)) {
-    same_site_context.context =
-        CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT;
+    same_site_context.set_context(
+        CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT);
   } else {
-    same_site_context.context =
-        CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE;
+    same_site_context.set_context(
+        CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE);
   }
-  same_site_context.cross_schemeness =
-      ComputeSchemeChange(url, site_for_cookies);
+  same_site_context.set_cross_schemeness(
+      ComputeSchemeChange(url, site_for_cookies));
   return same_site_context;
 }
 
diff --git a/net/docs/life-of-a-url-request.md b/net/docs/life-of-a-url-request.md
index 4bf89d2..3b0dcdc 100644
--- a/net/docs/life-of-a-url-request.md
+++ b/net/docs/life-of-a-url-request.md
@@ -25,15 +25,16 @@
 The top-level network stack object is the URLRequestContext. The context has
 non-owning pointers to everything needed to create and issue a URLRequest. The
 context must outlive all requests that use it. Creating a context is a rather
-complicated process, and it's recommended that most consumers use
-URLRequestContextBuilder to do this.
+complicated process usually managed by URLRequestContextBuilder.
 
 The primary use of the URLRequestContext is to create URLRequest objects using
 URLRequestContext::CreateRequest(). The URLRequest is the main interface used
-by direct consumers of the network stack. It use used to drive requests for
-http, https, ftp, and some data URLs. Each URLRequest tracks a single request
-across all redirects until an error occurs, it's canceled, or a final response
-is received, with a (possibly empty) body.
+by direct consumers of the network stack. It manages loading URLs with the
+http, https, ws, and wss schemes. URLs for other schemes, such as file,
+filesystem, blob, chrome, and data, are managed completely outside of //net.
+Each URLRequest tracks a single request across all redirects until an error
+occurs, it's canceled, or a final response is received, with a (possibly empty)
+body.
 
 The HttpNetworkSession is another major network stack object. It owns the
 HttpStreamFactory, the socket pools, and the HTTP/2 and QUIC session pools. It
@@ -64,13 +65,15 @@
 and provides cross-process network APIs and their implementations for the rest
 of Chrome. The network service uses the namespace "network" for all its classes.
 The Mojo interfaces it provides are in the network::mojom namespace. Mojo is
-Chrome's IPC layer. Generally there's a network::mojom::FooPtr proxy object in
-the consumer's process which also implements the network::mojom::Foo interface.
-When the proxy object's methods are invoked, it passes the call and all its
-arguments over a Mojo IPC channel to another the implementation of the
-network::mojom::Foo interface in the network service (typically implemented by a
-class named network::Foo), which may be running in another process, or possibly
-another thread in the consumer's process.
+Chrome's IPC layer. Generally there's a `mojo::Remote<network::mojom::Foo>`
+proxy object in the consumer's process which also implements
+the network::mojom::Foo interface. When the proxy object's methods are invoked,
+it passes the call and all its arguments over a Mojo IPC channel, using a
+`mojo::Receiver<network::mojom::Foo>`, to an implementation of the
+network::mojom::Foo interface in the network service (the implementation is
+typically a class named network::Foo), which may be running in another process,
+another thread in the consumer's process, or even the same thread in the
+consumer's process.
 
 The network::NetworkService object is singleton that is used by Chrome to create
 all other network service objects. The primary objects it is used to create are
@@ -97,16 +100,15 @@
 
 # Life of a Simple URLRequest
 
-A request for data is dispatched from some other process which results in
-creating a network::URLLoader in the network process. The URLLoader then
-creates a URLRequest to drive the request. A protocol-specific job
-(e.g. HTTP, data, file) is attached to the request. In the HTTP case, that job
-first checks the cache, and then creates a network connection object, if
-necessary, to actually fetch the data. That connection object interacts with
-network socket pools to potentially re-use sockets; the socket pools create and
-connect a socket if there is no appropriate existing socket. Once that socket
-exists, the HTTP request is dispatched, the response read and parsed, and the
-result returned back up the stack and sent over to the child process.
+A request for data is dispatched from some process, which results in creating
+a network::URLLoader in the network service (which, on desktop platform, is
+typically in its own process). The URLLoader then creates a URLRequest to
+drive the network request. That job first checks the HTTP cache, and then
+creates a network transaction object, if necessary, to actually fetch the data.
+That transaction tries to reuse a connection if available. If none is available,
+it creates a new one. Once it has established a connection, the HTTP request is
+dispatched, the response read and parsed, and the result returned back up the
+stack and sent over to the caller.
 
 Of course, it's not quite that simple :-}.
 
@@ -122,10 +124,12 @@
 
 Summary:
 
+* In the browser process, the network::mojom::NetworkContext interface is used
+to create a network::mojom::URLLoaderFactory.
 * A consumer (e.g. the content::ResourceDispatcher for Blink, the
 content::NavigationURLLoaderImpl for frame navigations, or a
 network::SimpleURLLoader) passes a network::ResourceRequest object and
-network::mojom::URLLoaderClient Mojo channel to a
+network::mojom::URLLoaderClient Mojo channel to the
 network::mojom::URLLoaderFactory, and tells it to create and start a
 network::mojom::URLLoader.
 * Mojo sends the network::ResourceRequest over an IPC pipe to a
@@ -140,17 +144,31 @@
 renderer processes are the ones that layout webpages and run HTML.
 
 The browser process creates the top level network::mojom::NetworkContext
-objects, and uses them to create network::mojom::URLLoaderFactories, which it
-can set some security-related options on, before vending them to child
-processes. Child processes can then use them to directly talk to the network
-service.
+objects. The NetworkContext interface is privileged and can only be accessed
+from the browser process. The browser process uses it to create
+network::mojom::URLLoaderFactories, which can then be passed to less
+privileged processes to allow them to load resources using the NetworkContext.
+To create a URLLoaderFactory, a network::mojom::URLLoaderFactoryParams object
+is passed to the NetworkContext to configure fields that other processes are
+not trusted to set, for security and privacy reasons.
 
-A consumer that wants to make a network request gets a URLLoaderFactory through
-some manner, assembles a bunch of parameters in the large ResourceRequest
-object, creates a network::mojom::URLLoaderClient Mojo channel for the
-network::mojom::URLLoader to use to talk back to it, and then passes them to
-the URLLoaderFactory, which returns a URLLoader object that it can use to
-manage the network request.
+One such field is the net::IsolationInfo field, which includes:
+* A net::NetworkIsolationKey, which is used to enforce the
+[privacy sandbox](https://www.chromium.org/Home/chromium-privacy/privacy-sandbox)
+in the network stack, separating network resources used by different sites in
+order to protect against tracking a user across sites.
+* A net::SiteForCookies, which is used to determine which site to send SameSite
+cookies for. SameSite cookies prevent cross-site attacks by only being
+accessible when that site is the top-level site.
+* How to update these values across redirects.
+
+A consumer, either in the browser process or a child process, that wants to
+make a network request gets a URLLoaderFactory from the browser process through
+some manner, assembles a bunch of parameters in the large
+network::ResourceRequest object, creates a network::mojom::URLLoaderClient Mojo
+channel for the network::mojom::URLLoader to use to talk back to it, and then
+passes them all to the URLLoaderFactory, which returns a URLLoader object that
+it can use to manage the network request.
 
 ### network::URLLoaderFactory sets up the request in the network service
 
@@ -160,18 +178,20 @@
 * network::URLLoader uses the network::NetworkContext's URLRequestContext to
 create and start a URLRequest.
 
-The URLLoaderFactory, along with all NetworkContexts and most of the network
-stack, lives on a single thread in the network service. It gets a reconstituted
-ResourceRequest object from the Mojo pipe, does some checks to make sure it
-can service the request, and if so, creates a URLLoader, passing the request and
-the NetworkContext associated with the URLLoaderFactory.
+The network::URLLoaderFactory, along with all NetworkContexts and most of the
+network stack, lives on a single thread in the network service. It gets a
+reconstituted ResourceRequest object from the network::mojom::URLLoaderFactory
+Mojo pipe, does some checks to make sure it can service the request, and if so,
+creates a URLLoader, passing the request and the NetworkContext associated with
+the URLLoaderFactory.
 
-The URLLoader then calls into a URLRequestContext to create the URLRequest. The
-URLRequestContext has pointers to all the network stack objects needed to issue
-the request over the network, such as the cache, cookie store, and host
-resolver. The URLLoader then calls into the ResourceScheduler, which may delay
-starting the request, based on priority and other activity. Eventually, the
-ResourceScheduler starts the request.
+The URLLoader then calls into the NetworkContext's net::URLRequestContext to
+create the URLRequest. The URLRequestContext has pointers to all the network
+stack objects needed to issue the request over the network, such as the cache,
+cookie store, and host resolver. The URLLoader then calls into the
+network::ResourceScheduler, which may delay starting the request, based on
+priority and other activity. Eventually, the ResourceScheduler starts the
+request.
 
 ### Check the cache, request an HttpStream
 
diff --git a/net/docs/proxy.md b/net/docs/proxy.md
index 5c4f19d..34551e96 100644
--- a/net/docs/proxy.md
+++ b/net/docs/proxy.md
@@ -918,3 +918,81 @@
 When setting the `Content-Type`, servers should prefer using a mime type of
 `application/x-ns-proxy-autoconfig` or `application/x-javascript-config`.
 However in practice, Chrome does not enforce the mime type.
+
+## Capturing a Net Log for debugging proxy resolution issues
+
+Issues in proxy resolution are best investigated using a Net Log.
+
+A good starting point is to follow the [general instructions for
+net-export](https://www.chromium.org/for-testers/providing-network-details),
+*and while the Net Log is being captured perform these steps*:
+
+1. Reproduce the failure (ex: load a URL that fails)
+2. If you can reproduce a success, do so (ex: load a different URL that succeeds).
+3. In a new tab, navigate to `chrome://net-internals/#proxy` and click both
+   buttons ("Re-apply settings" and "Clear bad proxies").
+4. Repeat step (1)
+5. Stop the Net Log and save the file.
+
+The resulting Net Log should have enough information to diagnose common
+problems. It can be attached to a bug report, or explored using the [Net Log
+Viewer](https://netlog-viewer.appspot.com/). See the next section for some tips
+on analyzing it.
+
+## Analyzing Net Logs for proxy issues
+
+Load saved Net Logs using [Net Log Viewer](https://netlog-viewer.appspot.com/).
+
+### Proxy overview tab
+
+Start by getting a big-picture view of the proxy settings by clicking to the
+"Proxy" tab on the left. This summarizes the proxy settings at the time the
+_capture ended_.
+
+* Does the _original_ proxy settings match expectation?
+  The proxy settings might be coming from:
+  * Managed Chrome policy (chrome://policy)
+  * Command line flags (ex: `--proxy-server`)
+  * (per-profile) Chrome extensions (ex: [chrome.proxy](https://developer.chrome.com/extensions/proxy))
+  * (per-network) System proxy settings
+
+* Was proxy autodetect (WPAD) specified? In this case the final URL probed will
+  be reflected by the difference between the "Effective" and "Original"
+  settings. A known issue with auto-detect is that DHCP based auto-detect is only
+  supported by Chrome running on Windows and Chrome OS.
+
+* Internally, proxy settings are per-NetworkContext. The proxy
+  overview tab shows settings for a *particular* NetworkContext, namely the
+  one associated with the Profile used to navigate to `chrome://net-export`. For
+  instance if the net-export was initiated from an Incognito window, it may
+  show different proxy settings here than a net-export capture initiated by a
+  non-Incognito window. When the net-export was triggered from command line
+  (`--log-net-log`) no particular NetworkContext is associated with the
+  capture and hence no proxy settings will be shown in this overview.
+
+* Were any proxies marked as bad?
+
+### Import tab
+
+Skim through the Import tab and look for relevant command line flags and active
+field trials. A find-in-page for `proxy` is a good starting point. Be on the lookout for
+`--winhttp-proxy-resolver` which has [known
+problems](https://bugs.chromium.org/p/chromium/issues/detail?id=644030).
+
+### Events tab
+
+To deep dive into proxy resolution, switch to the Events tab.
+
+You can start by filtering on `type:URL_REQUEST` to see all the top level
+requests, and then keep click through the dependency links to
+trace the proxy resolution steps and outcome.
+
+The most relevant events have either `PROXY_`, `PAC_`, or
+`WPAD_` in their names. You can also try filtering for each of those.
+
+Documentation on specific events is available in
+[net_log_event_type_list.h](https://chromium.googlesource.com/chromium/src/+/HEAD/net/log/net_log_event_type_list.h).
+
+Network change events can also be key to understanding proxy issues. After
+switching networks (ex VPN), the effective proxy settings, as well as content
+of any PAC scripts/auto-detect can change.
diff --git a/printing/BUILD.gn b/printing/BUILD.gn
index 54c76a54..6fce8a07 100644
--- a/printing/BUILD.gn
+++ b/printing/BUILD.gn
@@ -265,8 +265,8 @@
         "backend/print_backend_cups.h",
       ]
 
-      # We still build the utils for fuzzing.
-      if (use_fuzzing_engine) {
+      # We still build the utils for fuzzing if not already built.
+      if (use_fuzzing_engine && !use_cups_ipp) {
         sources += [
           "backend/cups_ipp_constants.cc",
           "backend/cups_ipp_constants.h",
diff --git a/services/device/usb/usb_device_handle_win.cc b/services/device/usb/usb_device_handle_win.cc
index ea340809..7a311a14 100644
--- a/services/device/usb/usb_device_handle_win.cc
+++ b/services/device/usb/usb_device_handle_win.cc
@@ -525,6 +525,16 @@
 
 UsbDeviceHandleWin::~UsbDeviceHandleWin() = default;
 
+void UsbDeviceHandleWin::UpdateFunctionPath(
+    int interface_number,
+    const base::string16& function_path) {
+  auto it = interfaces_.find(interface_number);
+  if (it == interfaces_.end())
+    return;
+
+  it->second.function_path = function_path;
+}
+
 bool UsbDeviceHandleWin::OpenInterfaceHandle(Interface* interface) {
   if (interface->handle.IsValid())
     return true;
diff --git a/services/device/usb/usb_device_handle_win.h b/services/device/usb/usb_device_handle_win.h
index f04c3df..c5cdc98 100644
--- a/services/device/usb/usb_device_handle_win.h
+++ b/services/device/usb/usb_device_handle_win.h
@@ -83,6 +83,9 @@
 
   ~UsbDeviceHandleWin() override;
 
+  void UpdateFunctionPath(int interface_number,
+                          const base::string16& function_path);
+
  private:
   class Request;
 
diff --git a/services/device/usb/usb_device_win.cc b/services/device/usb/usb_device_win.cc
index dea86aad..72159ac1 100644
--- a/services/device/usb/usb_device_win.cc
+++ b/services/device/usb/usb_device_win.cc
@@ -48,6 +48,9 @@
   else if (base::EqualsCaseInsensitiveASCII(driver_name_, L"usbccgp"))
     device_handle = new UsbDeviceHandleWin(this, /*composite=*/true);
 
+  if (device_handle)
+    handles().push_back(device_handle.get());
+
   base::SequencedTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(std::move(callback), device_handle));
 }
@@ -72,6 +75,18 @@
                                     std::move(callback), device_handle));
 }
 
+void UsbDeviceWin::UpdateFunctionPath(int interface_number,
+                                      const base::string16& function_path) {
+  function_paths_.insert({interface_number, function_path});
+
+  for (UsbDeviceHandle* handle : handles()) {
+    // This is safe because only this class only adds instance of
+    // UsbDeviceHandleWin to handles().
+    static_cast<UsbDeviceHandleWin*>(handle)->UpdateFunctionPath(
+        interface_number, function_path);
+  }
+}
+
 void UsbDeviceWin::OnReadDescriptors(
     base::OnceCallback<void(bool)> callback,
     scoped_refptr<UsbDeviceHandle> device_handle,
diff --git a/services/device/usb/usb_device_win.h b/services/device/usb/usb_device_win.h
index 2201f6f1..0e259a78 100644
--- a/services/device/usb/usb_device_win.h
+++ b/services/device/usb/usb_device_win.h
@@ -48,6 +48,9 @@
   // and string descriptors.
   void ReadDescriptors(base::OnceCallback<void(bool)> callback);
 
+  void UpdateFunctionPath(int interface_number,
+                          const base::string16& function_path);
+
  private:
   void OnReadDescriptors(base::OnceCallback<void(bool)> callback,
                          scoped_refptr<UsbDeviceHandle> device_handle,
@@ -77,7 +80,7 @@
 
   const base::string16 device_path_;
   const base::string16 hub_path_;
-  const base::flat_map<int, base::string16> function_paths_;
+  base::flat_map<int, base::string16> function_paths_;
   const base::string16 driver_name_;
 
   DISALLOW_COPY_AND_ASSIGN(UsbDeviceWin);
diff --git a/services/device/usb/usb_service_win.cc b/services/device/usb/usb_service_win.cc
index ad7b8528..f8eb64a 100644
--- a/services/device/usb/usb_service_win.cc
+++ b/services/device/usb/usb_service_win.cc
@@ -128,27 +128,40 @@
                                base::string16* device_path,
                                uint32_t* bus_number,
                                uint32_t* port_number,
+                               base::string16* instance_id,
                                base::string16* parent_instance_id,
                                std::vector<base::string16>* child_instance_ids,
                                base::string16* service_name) {
-  DWORD required_size = 0;
-  if (SetupDiGetDeviceInterfaceDetail(dev_info, device_interface_data, nullptr,
-                                      0, &required_size, nullptr) ||
-      GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
-    return false;
-  }
-
-  std::unique_ptr<SP_DEVICE_INTERFACE_DETAIL_DATA, base::FreeDeleter>
-  device_interface_detail_data(
-      static_cast<SP_DEVICE_INTERFACE_DETAIL_DATA*>(malloc(required_size)));
-  device_interface_detail_data->cbSize = sizeof(*device_interface_detail_data);
-
   SP_DEVINFO_DATA dev_info_data = {};
   dev_info_data.cbSize = sizeof(dev_info_data);
 
+  DWORD required_size = 0;
+  std::unique_ptr<SP_DEVICE_INTERFACE_DETAIL_DATA, base::FreeDeleter>
+      device_interface_detail_data;
+
+  // Probing for the required size of the SP_DEVICE_INTERFACE_DETAIL_DATA
+  // struct is only required if we are looking for the device path.
+  // Otherwise all the necessary data can be queried from the SP_DEVINFO_DATA.
+  if (device_path) {
+    if (!SetupDiGetDeviceInterfaceDetail(dev_info, device_interface_data,
+                                         /*DeviceInterfaceDetailData=*/nullptr,
+                                         /*DeviceInterfaceDetailDataSize=*/0,
+                                         &required_size,
+                                         /*DeviceInfoData=*/nullptr) &&
+        GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+      return false;
+    }
+
+    device_interface_detail_data.reset(
+        static_cast<SP_DEVICE_INTERFACE_DETAIL_DATA*>(malloc(required_size)));
+    device_interface_detail_data->cbSize =
+        sizeof(*device_interface_detail_data);
+  }
+
   if (!SetupDiGetDeviceInterfaceDetail(
           dev_info, device_interface_data, device_interface_detail_data.get(),
-          required_size, nullptr, &dev_info_data)) {
+          required_size, /*RequiredSize=*/nullptr, &dev_info_data) &&
+      (device_path || GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
     USB_PLOG(ERROR) << "SetupDiGetDeviceInterfaceDetail";
     return false;
   }
@@ -176,6 +189,16 @@
     *port_number = result.value();
   }
 
+  if (instance_id) {
+    auto result = GetDeviceStringProperty(dev_info, &dev_info_data,
+                                          DEVPKEY_Device_InstanceId);
+    if (!result.has_value()) {
+      USB_PLOG(ERROR) << "Failed to get the instance ID";
+      return false;
+    }
+    *instance_id = std::move(result.value());
+  }
+
   if (parent_instance_id) {
     auto result = GetDeviceStringProperty(dev_info, &dev_info_data,
                                           DEVPKEY_Device_Parent);
@@ -231,9 +254,11 @@
   }
 
   base::string16 device_path;
-  if (!GetDeviceInterfaceDetails(dev_info.get(), &device_interface_data,
-                                 &device_path, nullptr, nullptr, nullptr,
-                                 nullptr, nullptr)) {
+  if (!GetDeviceInterfaceDetails(
+          dev_info.get(), &device_interface_data, &device_path,
+          /*bus_number=*/nullptr, /*port_number=*/nullptr,
+          /*instance_id=*/nullptr, /*parent_instance_id=*/nullptr,
+          /*child_instance_ids=*/nullptr, /*service_name=*/nullptr)) {
     return base::string16();
   }
 
@@ -356,10 +381,9 @@
         FROM_HERE, base::BindOnce(&UsbServiceWin::HelperStarted, service_));
   }
 
-  void EnumerateDevicePath(const base::string16& device_path) {
-    ScopedDevInfo dev_info(
-        SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, nullptr, 0,
-                            DIGCF_DEVICEINTERFACE | DIGCF_PRESENT));
+  void OnDeviceAdded(const GUID& guid, const base::string16& device_path) {
+    ScopedDevInfo dev_info(SetupDiGetClassDevs(
+        &guid, nullptr, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT));
     if (!dev_info.is_valid()) {
       USB_PLOG(ERROR) << "Failed to set up device enumeration";
       return;
@@ -373,7 +397,12 @@
       return;
     }
 
-    EnumerateDevice(dev_info.get(), &device_interface_data, device_path);
+    if (IsEqualGUID(guid, GUID_DEVINTERFACE_USB_DEVICE)) {
+      EnumerateDevice(dev_info.get(), &device_interface_data, device_path);
+    } else {
+      EnumeratePotentialFunction(dev_info.get(), &device_interface_data,
+                                 device_path);
+    }
   }
 
   void EnumerateDevice(HDEVINFO dev_info,
@@ -393,8 +422,8 @@
     base::string16 service_name;
     if (!GetDeviceInterfaceDetails(dev_info, device_interface_data,
                                    device_path_ptr, &bus_number, &port_number,
-                                   &parent_instance_id, &child_instance_ids,
-                                   &service_name)) {
+                                   /*instance_id=*/nullptr, &parent_instance_id,
+                                   &child_instance_ids, &service_name)) {
       return;
     }
 
@@ -427,6 +456,39 @@
                                   port_number, std::move(service_name)));
   }
 
+  void EnumeratePotentialFunction(
+      HDEVINFO dev_info,
+      SP_DEVICE_INTERFACE_DATA* device_interface_data,
+      const base::string16& device_path) {
+    base::string16 instance_id;
+    base::string16 parent_instance_id;
+    base::string16 service_name;
+    if (!GetDeviceInterfaceDetails(
+            dev_info, device_interface_data,
+            /*device_path=*/nullptr, /*bus_number=*/nullptr,
+            /*port_number=*/nullptr, &instance_id, &parent_instance_id,
+            /*child_instance_ids=*/nullptr, &service_name)) {
+      return;
+    }
+
+    if (!base::EqualsCaseInsensitiveASCII(service_name, L"winusb"))
+      return;
+
+    int interface_number = GetInterfaceNumber(instance_id);
+    if (interface_number == -1)
+      return;
+
+    base::string16 parent_path =
+        GetDevicePath(parent_instance_id, GUID_DEVINTERFACE_USB_DEVICE);
+    if (parent_path.empty())
+      return;
+
+    service_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&UsbServiceWin::UpdateFunctionPath, service_,
+                                  std::move(parent_path), interface_number,
+                                  std::move(device_path)));
+  }
+
  private:
   std::unordered_map<base::string16, base::string16> hub_paths_;
 
@@ -441,8 +503,7 @@
       blocking_task_runner_(CreateBlockingTaskRunner()),
       helper_(nullptr, base::OnTaskRunnerDeleter(blocking_task_runner_)),
       device_observer_(this) {
-  DeviceMonitorWin* device_monitor =
-      DeviceMonitorWin::GetForDeviceInterface(GUID_DEVINTERFACE_USB_DEVICE);
+  DeviceMonitorWin* device_monitor = DeviceMonitorWin::GetForAllInterfaces();
   if (device_monitor)
     device_observer_.Add(device_monitor);
 
@@ -467,8 +528,9 @@
 void UsbServiceWin::OnDeviceAdded(const GUID& class_guid,
                                   const base::string16& device_path) {
   blocking_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&BlockingTaskRunnerHelper::EnumerateDevicePath,
-                                base::Unretained(helper_.get()), device_path));
+      FROM_HERE,
+      base::BindOnce(&BlockingTaskRunnerHelper::OnDeviceAdded,
+                     base::Unretained(helper_.get()), class_guid, device_path));
 }
 
 void UsbServiceWin::OnDeviceRemoved(const GUID& class_guid,
@@ -527,6 +589,20 @@
                                          weak_factory_.GetWeakPtr(), device));
 }
 
+void UsbServiceWin::UpdateFunctionPath(const base::string16& device_path,
+                                       int interface_number,
+                                       const base::string16& function_path) {
+  auto it = devices_by_path_.find(device_path);
+  if (it == devices_by_path_.end())
+    return;
+  const scoped_refptr<UsbDeviceWin>& device = it->second;
+
+  USB_LOG(EVENT) << "USB device function updated: guid=" << device->guid()
+                 << ", interface_number=" << interface_number << ", path=\""
+                 << device_path << "\"";
+  device->UpdateFunctionPath(interface_number, function_path);
+}
+
 void UsbServiceWin::DeviceReady(scoped_refptr<UsbDeviceWin> device,
                                 bool success) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/services/device/usb/usb_service_win.h b/services/device/usb/usb_service_win.h
index 942e70f..d470dad 100644
--- a/services/device/usb/usb_service_win.h
+++ b/services/device/usb/usb_service_win.h
@@ -48,6 +48,9 @@
       uint32_t bus_number,
       uint32_t port_number,
       const base::string16& driver_name);
+  void UpdateFunctionPath(const base::string16& device_path,
+                          int interface_number,
+                          const base::string16& function_path);
 
   void DeviceReady(scoped_refptr<UsbDeviceWin> device, bool success);
 
diff --git a/services/network/public/cpp/cookie_manager_mojom_traits.h b/services/network/public/cpp/cookie_manager_mojom_traits.h
index 3a9814d..a04a0f0 100644
--- a/services/network/public/cpp/cookie_manager_mojom_traits.h
+++ b/services/network/public/cpp/cookie_manager_mojom_traits.h
@@ -81,12 +81,12 @@
                     net::CookieOptions::SameSiteCookieContext> {
   static net::CookieOptions::SameSiteCookieContext::ContextType context(
       net::CookieOptions::SameSiteCookieContext& s) {
-    return s.context;
+    return s.context();
   }
 
   static net::CookieOptions::SameSiteCookieContext::CrossSchemeness
   cross_schemeness(net::CookieOptions::SameSiteCookieContext& s) {
-    return s.cross_schemeness;
+    return s.cross_schemeness();
   }
 
   static bool Read(network::mojom::CookieSameSiteContextDataView mojo_options,
diff --git a/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc b/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc
index 8b5180c9..70d78fda 100644
--- a/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc
@@ -202,8 +202,7 @@
           mojo::test::SerializeAndDeserialize<mojom::CookieSameSiteContext>(
               &context_in, &copy));
 
-      EXPECT_EQ(context_in.context, copy.context);
-      EXPECT_EQ(context_in.cross_schemeness, copy.cross_schemeness);
+      EXPECT_EQ(context_in, copy);
     }
   }
 }
diff --git a/services/network/public/cpp/cross_origin_embedder_policy.cc b/services/network/public/cpp/cross_origin_embedder_policy.cc
index aa2c3ee7..dd6c6108 100644
--- a/services/network/public/cpp/cross_origin_embedder_policy.cc
+++ b/services/network/public/cpp/cross_origin_embedder_policy.cc
@@ -4,8 +4,21 @@
 
 #include "services/network/public/cpp/cross_origin_embedder_policy.h"
 
+#include <algorithm>
+
+#include "net/http/structured_headers.h"
+
 namespace network {
 
+namespace {
+constexpr char kRequireCorp[] = "require-corp";
+}  // namespace
+
+const char CrossOriginEmbedderPolicy::kHeaderName[] =
+    "cross-origin-embedder-policy";
+const char CrossOriginEmbedderPolicy::kReportOnlyHeaderName[] =
+    "cross-origin-embedder-policy-report-only";
+
 CrossOriginEmbedderPolicy::CrossOriginEmbedderPolicy() = default;
 CrossOriginEmbedderPolicy::CrossOriginEmbedderPolicy(
     const CrossOriginEmbedderPolicy& src) = default;
@@ -25,4 +38,25 @@
          report_only_reporting_endpoint == other.report_only_reporting_endpoint;
 }
 
+std::pair<mojom::CrossOriginEmbedderPolicyValue, base::Optional<std::string>>
+CrossOriginEmbedderPolicy::Parse(base::StringPiece header_value) {
+  constexpr auto kNone = mojom::CrossOriginEmbedderPolicyValue::kNone;
+  using Item = net::structured_headers::Item;
+  const auto item = net::structured_headers::ParseItem(header_value);
+  if (!item || item->item.Type() != Item::kTokenType ||
+      item->item.GetString() != kRequireCorp) {
+    return std::make_pair(kNone, base::nullopt);
+  }
+  base::Optional<std::string> endpoint;
+  auto it = std::find_if(item->params.cbegin(), item->params.cend(),
+                         [](const std::pair<std::string, Item>& param) {
+                           return param.first == "report-to";
+                         });
+  if (it != item->params.end() && it->second.Type() == Item::kStringType) {
+    endpoint = it->second.GetString();
+  }
+  return std::make_pair(mojom::CrossOriginEmbedderPolicyValue::kRequireCorp,
+                        std::move(endpoint));
+}
+
 }  // namespace network
diff --git a/services/network/public/cpp/cross_origin_embedder_policy.h b/services/network/public/cpp/cross_origin_embedder_policy.h
index ea1e12c6..db9cc28 100644
--- a/services/network/public/cpp/cross_origin_embedder_policy.h
+++ b/services/network/public/cpp/cross_origin_embedder_policy.h
@@ -6,9 +6,11 @@
 #define SERVICES_NETWORK_PUBLIC_CPP_CROSS_ORIGIN_EMBEDDER_POLICY_H_
 
 #include <string>
+#include <utility>
 
 #include "base/component_export.h"
 #include "base/optional.h"
+#include "base/strings/string_piece.h"
 #include "services/network/public/mojom/cross_origin_embedder_policy.mojom-shared.h"
 
 namespace network {
@@ -30,6 +32,21 @@
   mojom::CrossOriginEmbedderPolicyValue report_only_value =
       mojom::CrossOriginEmbedderPolicyValue::kNone;
   base::Optional<std::string> report_only_reporting_endpoint;
+
+  // Parses |header_value| and returns a pair of a COEP value and an optional
+  // reporting endpoint. This is usually used for two headers.
+  //
+  //   CrossOriginEmbedderPolicyValue coep;
+  //   std::tie(coep.value, coep.reporting_endpoint) =
+  //     CrossOriginEmbedderPolicyValue::Parse(header_value);
+  //   std::tie(coep.report_only_value, coep.report_only_reporting_endpoint) =
+  //     CrossOriginEmbedderPolicyValue::Parse(report_only_header_value);
+  static std::pair<mojom::CrossOriginEmbedderPolicyValue,
+                   base::Optional<std::string>>
+  Parse(base::StringPiece header_value);
+
+  static const char kHeaderName[];
+  static const char kReportOnlyHeaderName[];
 };
 
 }  // namespace network
diff --git a/services/network/public/mojom/url_loader.mojom b/services/network/public/mojom/url_loader.mojom
index 97f9b07..8781ed0 100644
--- a/services/network/public/mojom/url_loader.mojom
+++ b/services/network/public/mojom/url_loader.mojom
@@ -120,6 +120,14 @@
   // bypassed by setting site_for_cookies = SiteForCookies::FromUrl(url), but
   // this should only be done if the fetch can be reasonably said to be done by
   // the same principal as what |url| represents.
+  //
+  // Currently if a renderer is compromised an attacker could alter the
+  // SiteForCookies. This would allow the renderer to send cookies to a
+  // third-party context when it otherwise wouldn't be able to.
+  // https://crbug.com/1060631 will move the SFC computation into the browser
+  // process to prevent this.
+  // TODO(https://crbug.com/1060631): Remove this message after the fix is
+  // landed.
   SiteForCookies site_for_cookies;
 
   // Boolean indicating whether SameSite cookies are allowed to be attached
diff --git a/services/network/trust_tokens/BUILD.gn b/services/network/trust_tokens/BUILD.gn
index bb2273c7..6227942 100644
--- a/services/network/trust_tokens/BUILD.gn
+++ b/services/network/trust_tokens/BUILD.gn
@@ -15,6 +15,8 @@
   defines = [ "IS_NETWORK_SERVICE_IMPL" ]
 
   sources = [
+    "ed25519_key_pair_generator.cc",
+    "ed25519_key_pair_generator.h",
     "ed25519_trust_token_request_signer.cc",
     "ed25519_trust_token_request_signer.h",
     "has_trust_tokens_answerer.cc",
@@ -100,6 +102,7 @@
   defines = [ "IS_NETWORK_SERVICE_IMPL" ]
 
   sources = [
+    "ed25519_key_pair_generator_unittest.cc",
     "ed25519_trust_token_request_signer_unittest.cc",
     "has_trust_tokens_answerer_unittest.cc",
     "pending_trust_token_store_unittest.cc",
diff --git a/services/network/trust_tokens/ed25519_key_pair_generator.cc b/services/network/trust_tokens/ed25519_key_pair_generator.cc
new file mode 100644
index 0000000..b1f72c4
--- /dev/null
+++ b/services/network/trust_tokens/ed25519_key_pair_generator.cc
@@ -0,0 +1,25 @@
+// 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 "services/network/trust_tokens/ed25519_key_pair_generator.h"
+
+#include "base/containers/span.h"
+#include "third_party/boringssl/src/include/openssl/curve25519.h"
+
+namespace network {
+
+bool Ed25519KeyPairGenerator::Generate(std::string* signing_key_out,
+                                       std::string* verification_key_out) {
+  signing_key_out->resize(ED25519_PRIVATE_KEY_LEN);
+  verification_key_out->resize(ED25519_PUBLIC_KEY_LEN);
+
+  // This can't fail.
+  ED25519_keypair(
+      base::as_writable_bytes(base::make_span(*verification_key_out)).data(),
+      base::as_writable_bytes(base::make_span(*signing_key_out)).data());
+
+  return true;
+}
+
+}  // namespace network
diff --git a/services/network/trust_tokens/ed25519_key_pair_generator.h b/services/network/trust_tokens/ed25519_key_pair_generator.h
new file mode 100644
index 0000000..928389f
--- /dev/null
+++ b/services/network/trust_tokens/ed25519_key_pair_generator.h
@@ -0,0 +1,27 @@
+// 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 SERVICES_NETWORK_TRUST_TOKENS_ED25519_KEY_PAIR_GENERATOR_H_
+#define SERVICES_NETWORK_TRUST_TOKENS_ED25519_KEY_PAIR_GENERATOR_H_
+
+#include "services/network/trust_tokens/trust_token_request_redemption_helper.h"
+
+namespace network {
+
+// Ed25519KeyPairGenerator generates an Ed25519 key pair in BoringSSL's
+// native encoding.
+class Ed25519KeyPairGenerator
+    : public TrustTokenRequestRedemptionHelper::KeyPairGenerator {
+ public:
+  Ed25519KeyPairGenerator() = default;
+  ~Ed25519KeyPairGenerator() override = default;
+
+  // TrustTokenRequestRedemptionHelper::KeyPairGenerator implementation:
+  bool Generate(std::string* signing_key_out,
+                std::string* verification_key_out) override;
+};
+
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_TRUST_TOKENS_ED25519_KEY_PAIR_GENERATOR_H_
diff --git a/services/network/trust_tokens/ed25519_key_pair_generator_unittest.cc b/services/network/trust_tokens/ed25519_key_pair_generator_unittest.cc
new file mode 100644
index 0000000..fa07504
--- /dev/null
+++ b/services/network/trust_tokens/ed25519_key_pair_generator_unittest.cc
@@ -0,0 +1,31 @@
+// 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 "services/network/trust_tokens/ed25519_key_pair_generator.h"
+#include "base/containers/span.h"
+#include "services/network/trust_tokens/ed25519_trust_token_request_signer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace network {
+
+TEST(Ed25519KeyPairGenerator, Roundtrip) {
+  auto message = base::as_bytes(base::make_span(
+      "Four score and seven years ago our fathers brought forth on this "
+      "continent, a new nation, conceived in Liberty, and dedicated to the "
+      "proposition that all men are created equal."));
+
+  std::string signing, verification;
+  ASSERT_TRUE(Ed25519KeyPairGenerator().Generate(&signing, &verification));
+
+  Ed25519TrustTokenRequestSigner signer;
+
+  base::Optional<std::vector<uint8_t>> signature =
+      signer.Sign(base::as_bytes(base::make_span(signing)), message);
+  ASSERT_TRUE(signature);
+
+  EXPECT_TRUE(signer.Verify(message, *signature,
+                            base::as_bytes(base::make_span(verification))));
+}
+
+}  // namespace network
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index c468df4..2664641 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -76,12 +76,7 @@
 // mojo::core::Core::CreateDataPipe
 constexpr size_t kBlockedBodyAllocationSize = 1;
 
-constexpr char kCrossOriginEmbedderPolicyValueHeader[] =
-    "Cross-Origin-Embedder-Policy";
-constexpr char kCrossOriginEmbedderPolicyValueReportOnlyHeader[] =
-    "Cross-Origin-Embedder-Policy-Report-Only";
 constexpr char kCrossOriginOpenerPolicyHeader[] = "Cross-Origin-Opener-Policy";
-constexpr char kRequireCorp[] = "require-corp";
 
 // TODO: this duplicates some of PopulateResourceResponse in
 // content/browser/loader/resource_loader.cc
@@ -446,28 +441,13 @@
 ParseCrossOriginEmbedderPolicyValueInternal(
     const net::HttpResponseHeaders* headers,
     base::StringPiece header_name) {
-  constexpr auto kNone = mojom::CrossOriginEmbedderPolicyValue::kNone;
-  using Item = net::structured_headers::Item;
   std::string header_value;
   if (!headers ||
       !headers->GetNormalizedHeader(header_name.as_string(), &header_value)) {
-    return std::make_pair(kNone, base::nullopt);
+    return std::make_pair(mojom::CrossOriginEmbedderPolicyValue::kNone,
+                          base::nullopt);
   }
-  const auto item = net::structured_headers::ParseItem(header_value);
-  if (!item || item->item.Type() != Item::kTokenType ||
-      item->item.GetString() != kRequireCorp) {
-    return std::make_pair(kNone, base::nullopt);
-  }
-  base::Optional<std::string> endpoint;
-  auto it = std::find_if(item->params.cbegin(), item->params.cend(),
-                         [](const std::pair<std::string, Item>& param) {
-                           return param.first == "report-to";
-                         });
-  if (it != item->params.end() && it->second.Type() == Item::kStringType) {
-    endpoint = it->second.GetString();
-  }
-  return std::make_pair(mojom::CrossOriginEmbedderPolicyValue::kRequireCorp,
-                        std::move(endpoint));
+  return CrossOriginEmbedderPolicy::Parse(header_value);
 }
 
 }  // namespace
@@ -1547,10 +1527,10 @@
   CrossOriginEmbedderPolicy coep;
   std::tie(coep.value, coep.reporting_endpoint) =
       ParseCrossOriginEmbedderPolicyValueInternal(
-          headers, kCrossOriginEmbedderPolicyValueHeader);
+          headers, CrossOriginEmbedderPolicy::kHeaderName);
   std::tie(coep.report_only_value, coep.report_only_reporting_endpoint) =
       ParseCrossOriginEmbedderPolicyValueInternal(
-          headers, kCrossOriginEmbedderPolicyValueReportOnlyHeader);
+          headers, CrossOriginEmbedderPolicy::kReportOnlyHeaderName);
   return coep;
 }
 
diff --git a/services/tracing/perfetto/privacy_filtered_fields-inl.h b/services/tracing/perfetto/privacy_filtered_fields-inl.h
index 2c5713c..f43629e 100644
--- a/services/tracing/perfetto/privacy_filtered_fields-inl.h
+++ b/services/tracing/perfetto/privacy_filtered_fields-inl.h
@@ -135,6 +135,11 @@
 constexpr int kChromeUserEventIndices[] = {2, -1};
 constexpr MessageInfo kChromeUserEvent = {kChromeUserEventIndices, nullptr};
 
+// Proto Message: ChromeKeyedService
+constexpr int kChromeKeyedServiceIndices[] = {1, -1};
+constexpr MessageInfo kChromeKeyedService = {kChromeKeyedServiceIndices,
+                                             nullptr};
+
 // Proto Message: ChromeLegacyIpc
 constexpr int kChromeLegacyIpcIndices[] = {1, 2, -1};
 constexpr MessageInfo kChromeLegacyIpc = {kChromeLegacyIpcIndices, nullptr};
@@ -146,25 +151,18 @@
 
 // Proto Message: ComponentInfo
 constexpr int kComponentInfoIndices[] = {1, 2, -1};
-constexpr MessageInfo kComponentInfo = {
-    kComponentInfoIndices,
-    nullptr};
+constexpr MessageInfo kComponentInfo = {kComponentInfoIndices, nullptr};
 
 // Proto Message: ChromeLatencyInfo
 constexpr int kChromeLatencyInfoIndices[] = {1, 2, 3, 4, 5, -1};
 constexpr MessageInfo const* kChromeLatencyInfoComplexMessages[] = {
-    nullptr,
-    nullptr,
-    nullptr,
-    &kComponentInfo,
-    nullptr};
+    nullptr, nullptr, nullptr, &kComponentInfo, nullptr};
 constexpr MessageInfo kChromeLatencyInfo = {kChromeLatencyInfoIndices,
                                             kChromeLatencyInfoComplexMessages};
 
 // Proto Message: TrackEvent
-// EDIT: Manually whitelisted: 29 (chrome_latency_info).
-constexpr int kTrackEventIndices[] = {1,  2,  3,  5,  6,  9,  10, 11,
-                                      16, 17, 24, 25, 27, 28, 29, -1};
+constexpr int kTrackEventIndices[] = {1,  2,  3,  5,  6,  9,  10, 11, 12, 16,
+                                      17, 24, 25, 26, 27, 28, 29, 30, 31, -1};
 constexpr MessageInfo const* kTrackEventComplexMessages[] = {
     nullptr,
     nullptr,
@@ -176,11 +174,15 @@
     nullptr,
     nullptr,
     nullptr,
+    nullptr,
     &kChromeCompositorSchedulerState,
     &kChromeUserEvent,
+    &kChromeKeyedService,
     &kChromeLegacyIpc,
     &kChromeHistogramSample,
-    &kChromeLatencyInfo};
+    &kChromeLatencyInfo,
+    nullptr,
+    nullptr};
 constexpr MessageInfo kTrackEvent = {kTrackEventIndices,
                                      kTrackEventComplexMessages};
 
@@ -303,7 +305,8 @@
                                     kHeapGraphComplexMessages};
 
 // Proto Message: TrackEventDefaults
-constexpr int kTrackEventDefaultsIndices[] = {11, -1};
+// Manually whitelisted: 31.
+constexpr int kTrackEventDefaultsIndices[] = {11, 31, -1};
 constexpr MessageInfo kTrackEventDefaults = {kTrackEventDefaultsIndices,
                                              nullptr};
 
@@ -324,11 +327,20 @@
 constexpr MessageInfo kChromeThreadDescriptor = {kChromeThreadDescriptorIndices,
                                                  nullptr};
 
+// Proto Message: CounterDescriptor
+constexpr int kCounterDescriptorIndices[] = {1, 3, 4, 5, -1};
+constexpr MessageInfo kCounterDescriptor = {kCounterDescriptorIndices, nullptr};
+
 // Proto Message: TrackDescriptor
-constexpr int kTrackDescriptorIndices[] = {1, 3, 4, 5, 6, 7, -1};
+constexpr int kTrackDescriptorIndices[] = {1, 3, 4, 5, 6, 7, 8, -1};
 constexpr MessageInfo const* kTrackDescriptorComplexMessages[] = {
-    nullptr, &kProcessDescriptor,       &kThreadDescriptor,
-    nullptr, &kChromeProcessDescriptor, &kChromeThreadDescriptor};
+    nullptr,
+    &kProcessDescriptor,
+    &kThreadDescriptor,
+    nullptr,
+    &kChromeProcessDescriptor,
+    &kChromeThreadDescriptor,
+    &kCounterDescriptor};
 constexpr MessageInfo kTrackDescriptor = {kTrackDescriptorIndices,
                                           kTrackDescriptorComplexMessages};
 
diff --git a/services/tracing/public/cpp/perfetto/perfetto_config.cc b/services/tracing/public/cpp/perfetto/perfetto_config.cc
index 9d79fe5..8f0cce3e 100644
--- a/services/tracing/public/cpp/perfetto/perfetto_config.cc
+++ b/services/tracing/public/cpp/perfetto/perfetto_config.cc
@@ -70,6 +70,7 @@
   auto* builtin_data_sources = perfetto_config.mutable_builtin_data_sources();
   builtin_data_sources->set_disable_trace_config(privacy_filtering_enabled);
   builtin_data_sources->set_disable_system_info(privacy_filtering_enabled);
+  builtin_data_sources->set_disable_service_events(privacy_filtering_enabled);
 
   // Clear incremental state every 5 seconds, so that we lose at most the first
   // 5 seconds of the trace (if we wrap around perfetto's central buffer).
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc b/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc
index c44c9ce..4a04d23 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc
@@ -45,6 +45,7 @@
 #include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h"
 #include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_process_descriptor.pb.h"
 #include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_thread_descriptor.pb.h"
+#include "third_party/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pb.h"
 #include "third_party/perfetto/protos/perfetto/trace/track_event/log_message.pbzero.h"
 #include "third_party/perfetto/protos/perfetto/trace/track_event/process_descriptor.pb.h"
 #include "third_party/perfetto/protos/perfetto/trace/track_event/thread_descriptor.pb.h"
@@ -63,6 +64,12 @@
 constexpr uint32_t kClockIdAbsolute = 64;
 constexpr uint32_t kClockIdIncremental = 65;
 
+base::ThreadTicks ThreadNow() {
+  return base::ThreadTicks::IsSupported()
+             ? base::subtle::ThreadTicksNowIgnoringOverride()
+             : base::ThreadTicks();
+}
+
 class MockProducerClient : public ProducerClient {
  public:
   explicit MockProducerClient(
@@ -341,9 +348,23 @@
         packet->trace_packet_defaults().track_event_defaults().track_uuid(),
         0u);
 
+    if (base::ThreadTicks::IsSupported()) {
+      EXPECT_GT(packet->trace_packet_defaults()
+                    .track_event_defaults()
+                    .extra_counter_track_uuids_size(),
+                0);
+    }
+
     default_track_uuid_ =
         packet->trace_packet_defaults().track_event_defaults().track_uuid();
 
+    default_extra_counter_track_uuids_.clear();
+    for (const auto& uuid : packet->trace_packet_defaults()
+                                .track_event_defaults()
+                                .extra_counter_track_uuids()) {
+      default_extra_counter_track_uuids_.push_back(uuid);
+    }
+
     // ClockSnapshot is only emitted when incremental state was reset, and
     // thus also always serves as indicator for the state reset to the consumer.
     EXPECT_EQ(packet->sequence_flags(),
@@ -353,7 +374,6 @@
 
   void ExpectThreadTrack(const perfetto::protos::TracePacket* packet,
                          uint64_t min_timestamp = 1u,
-                         uint64_t min_thread_time = 1u,
                          bool filtering_enabled = false) {
     ASSERT_TRUE(packet->has_track_descriptor());
     ASSERT_TRUE(packet->track_descriptor().has_thread());
@@ -372,13 +392,8 @@
 
     EXPECT_FALSE(
         packet->track_descriptor().thread().has_reference_timestamp_us());
-
-    EXPECT_GE(packet->track_descriptor().thread().reference_thread_time_us(),
-              last_thread_time_);
-    if (base::ThreadTicks::IsSupported()) {
-      EXPECT_LE(packet->track_descriptor().thread().reference_thread_time_us(),
-                base::ThreadTicks::Now().since_origin().InMicroseconds());
-    }
+    EXPECT_FALSE(
+        packet->track_descriptor().thread().has_reference_thread_time_us());
 
     if (filtering_enabled) {
       EXPECT_FALSE(packet->track_descriptor().thread().has_thread_name());
@@ -389,15 +404,29 @@
     EXPECT_EQ(perfetto::protos::ChromeThreadDescriptor::THREAD_MAIN,
               packet->track_descriptor().chrome_thread().thread_type());
 
-    last_thread_time_ =
-        packet->track_descriptor().thread().reference_thread_time_us();
-
     EXPECT_EQ(packet->interned_data().event_categories_size(), 0);
     EXPECT_EQ(packet->interned_data().event_names_size(), 0);
 
     EXPECT_EQ(packet->sequence_flags(), 0u);
   }
 
+  void ExpectThreadTimeCounterTrack(
+      const perfetto::protos::TracePacket* packet) {
+    EXPECT_NE(packet->track_descriptor().uuid(), 0u);
+    EXPECT_NE(packet->track_descriptor().parent_uuid(), 0u);
+
+    EXPECT_EQ(packet->track_descriptor().uuid(),
+              default_extra_counter_track_uuids_[0]);
+    EXPECT_EQ(packet->track_descriptor().parent_uuid(), default_track_uuid_);
+
+    EXPECT_EQ(packet->track_descriptor().counter().type(),
+              perfetto::protos::CounterDescriptor::COUNTER_THREAD_TIME_NS);
+    EXPECT_EQ(packet->track_descriptor().counter().unit_multiplier(), 1000u);
+    EXPECT_TRUE(packet->track_descriptor().counter().is_incremental());
+
+    last_thread_time_ = 0;
+  }
+
   void ExpectProcessTrack(const perfetto::protos::TracePacket* packet,
                           bool filtering_enabled = false) {
     ASSERT_TRUE(packet->has_track_descriptor());
@@ -429,20 +458,23 @@
                                         SEQ_INCREMENTAL_STATE_CLEARED));
   }
 
-  void ExpectStandardPreamble(size_t first_packet_index = 0,
-                              bool privacy_filtering_enabled = false) {
-    EXPECT_GE(producer_client()->GetFinalizedPacketCount(), 3u);
-
-    auto* pt_packet = producer_client()->GetFinalizedPacket(first_packet_index);
+  size_t ExpectStandardPreamble(size_t packet_index = 0,
+                                bool privacy_filtering_enabled = false) {
+    auto* pt_packet = producer_client()->GetFinalizedPacket(packet_index++);
     ExpectProcessTrack(pt_packet, privacy_filtering_enabled);
 
-    auto* clock_packet =
-        producer_client()->GetFinalizedPacket(first_packet_index + 1);
+    auto* clock_packet = producer_client()->GetFinalizedPacket(packet_index++);
     ExpectClockSnapshotAndDefaults(clock_packet);
 
-    auto* tt_packet =
-        producer_client()->GetFinalizedPacket(first_packet_index + 2);
-    ExpectThreadTrack(tt_packet, 1u, 1u, privacy_filtering_enabled);
+    auto* tt_packet = producer_client()->GetFinalizedPacket(packet_index++);
+    ExpectThreadTrack(tt_packet, 1u, privacy_filtering_enabled);
+
+    if (base::ThreadTicks::IsSupported()) {
+      auto* ttt_packet = producer_client()->GetFinalizedPacket(packet_index++);
+      ExpectThreadTimeCounterTrack(ttt_packet);
+    }
+
+    return packet_index;
   }
 
   void ExpectTraceEvent(const perfetto::protos::TracePacket* packet,
@@ -474,11 +506,14 @@
                     TRACE_TIME_TICKS_NOW().since_origin().InMicroseconds()));
       last_timestamp_ += packet->timestamp();
     }
-    if (packet->track_event().has_thread_time_delta_us()) {
-      EXPECT_LE(
-          last_thread_time_ + packet->track_event().thread_time_delta_us(),
-          TRACE_TIME_TICKS_NOW().since_origin().InMicroseconds());
-      last_thread_time_ += packet->track_event().thread_time_delta_us();
+
+    EXPECT_EQ(packet->track_event().extra_counter_track_uuids_size(), 0);
+    if (packet->track_event().extra_counter_values_size()) {
+      int64_t thread_time_delta =
+          packet->track_event().extra_counter_values()[0];
+      EXPECT_LE(last_thread_time_ + thread_time_delta,
+                TRACE_TIME_TICKS_NOW().since_origin().InMicroseconds());
+      last_thread_time_ += thread_time_delta;
     }
 
     if (category_iid > 0) {
@@ -670,6 +705,7 @@
   int64_t last_thread_time_ = 0;
   uint64_t default_track_uuid_ = 0u;
   uint64_t process_track_uuid_ = 0u;
+  std::vector<uint64_t> default_extra_counter_track_uuids_;
 
   std::string old_thread_name_;
   std::string old_process_name_;
@@ -813,10 +849,9 @@
 
   TRACE_EVENT_BEGIN0(kCategoryGroup, "bar");
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_BEGIN);
 
@@ -853,10 +888,9 @@
       kCategoryGroup, "bar", 42, 4242,
       base::TimeTicks() + base::TimeDelta::FromMicroseconds(424242));
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(
       e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
       TRACE_EVENT_PHASE_ASYNC_BEGIN,
@@ -872,10 +906,9 @@
 
   TRACE_EVENT_INSTANT0(kCategoryGroup, "bar", TRACE_EVENT_SCOPE_THREAD);
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -889,10 +922,9 @@
   TRACE_EVENT_INSTANT2(kCategoryGroup, "bar", TRACE_EVENT_SCOPE_THREAD,
                        "arg1_name", "arg1_val", "arg2_name", "arg2_val");
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -915,10 +947,9 @@
                        TRACE_EVENT_SCOPE_THREAD | TRACE_EVENT_FLAG_COPY,
                        "arg1_name", "arg1_val", "arg2_name", "arg2_val");
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT,
                    TRACE_EVENT_SCOPE_THREAD | TRACE_EVENT_FLAG_COPY);
@@ -941,10 +972,9 @@
   TRACE_EVENT_INSTANT2(kCategoryGroup, "bar", TRACE_EVENT_SCOPE_THREAD, "foo",
                        42u, "bar", 4242u);
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -960,10 +990,9 @@
   TRACE_EVENT_INSTANT2(kCategoryGroup, "bar", TRACE_EVENT_SCOPE_THREAD, "foo",
                        42, "bar", 4242);
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -979,10 +1008,9 @@
   TRACE_EVENT_INSTANT2(kCategoryGroup, "bar", TRACE_EVENT_SCOPE_THREAD, "foo",
                        true, "bar", false);
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1000,10 +1028,9 @@
   TRACE_EVENT_INSTANT2(kCategoryGroup, "bar", TRACE_EVENT_SCOPE_THREAD, "foo",
                        42.42, "bar", 4242.42);
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1020,10 +1047,9 @@
                        reinterpret_cast<void*>(0xBEEF), "bar",
                        reinterpret_cast<void*>(0xF00D));
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1066,10 +1092,9 @@
 
   EXPECT_EQ(2, num_calls);
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1091,10 +1116,9 @@
       TRACE_EVENT_SCOPE_THREAD | TRACE_EVENT_FLAG_TYPED_PROTO_ARGS, "src_file",
       "my_file", "src_func", "my_func");
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 5u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1108,7 +1132,7 @@
   EXPECT_EQ(locations[0].function_name(), "my_func");
 
   // Second event should refer to the same interning entries.
-  auto* e_packet2 = producer_client()->GetFinalizedPacket(4);
+  auto* e_packet2 = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet2, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1124,10 +1148,9 @@
       TRACE_EVENT_SCOPE_THREAD | TRACE_EVENT_FLAG_TYPED_PROTO_ARGS, "src",
       "my_file");
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1162,10 +1185,9 @@
       trace_event_trace_id.id_flags() | TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP,
       trace_event_internal::kNoId);
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* b_packet = producer_client()->GetFinalizedPacket(3);
+  auto* b_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(
       b_packet, /*category_iid=*/1u, /*name_iid=*/1u, TRACE_EVENT_PHASE_BEGIN,
       TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP | TRACE_EVENT_FLAG_HAS_ID, /*id=*/0u,
@@ -1177,12 +1199,10 @@
   base::trace_event::TraceLog::GetInstance()->UpdateTraceEventDurationExplicit(
       category_group_enabled, kEventName, handle, /*thread_id=*/1,
       /*explicit_timestamps=*/true,
-      base::TimeTicks() + base::TimeDelta::FromMicroseconds(30),
-      base::ThreadTicks() + base::TimeDelta::FromMicroseconds(50),
+      base::TimeTicks() + base::TimeDelta::FromMicroseconds(30), ThreadNow(),
       base::trace_event::ThreadInstructionCount());
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 5u);
-  auto* e_packet = producer_client()->GetFinalizedPacket(4);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(
       e_packet, /*category_iid=*/0u, /*name_iid=*/0u, TRACE_EVENT_PHASE_END,
       TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP, /*id=*/0u,
@@ -1194,12 +1214,10 @@
   base::trace_event::TraceLog::GetInstance()->UpdateTraceEventDurationExplicit(
       category_group_enabled, "other_event_name", handle, /*thread_id=*/1,
       /*explicit_timestamps=*/true,
-      base::TimeTicks() + base::TimeDelta::FromMicroseconds(40),
-      base::ThreadTicks() + base::TimeDelta::FromMicroseconds(60),
+      base::TimeTicks() + base::TimeDelta::FromMicroseconds(40), ThreadNow(),
       base::trace_event::ThreadInstructionCount());
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 6u);
-  auto* e2_packet = producer_client()->GetFinalizedPacket(5);
+  auto* e2_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(
       e2_packet, /*category_iid=*/0u, /*name_iid=*/0u, TRACE_EVENT_PHASE_END,
       TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP, /*id=*/0u,
@@ -1214,16 +1232,16 @@
 TEST_F(TraceEventDataSourceTest, InternedStrings) {
   CreateTraceEventDataSource();
 
+  size_t packet_index = 0u;
   for (size_t i = 0; i < 2; i++) {
     TRACE_EVENT_INSTANT1("cat1", "e1", TRACE_EVENT_SCOPE_THREAD, "arg1", 4);
     TRACE_EVENT_INSTANT1("cat1", "e1", TRACE_EVENT_SCOPE_THREAD, "arg1", 2);
     TRACE_EVENT_INSTANT1("cat2", "e2", TRACE_EVENT_SCOPE_THREAD, "arg2", 1);
 
-    EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 6u * (i + 1));
-    ExpectStandardPreamble(6 * i);  // 3 preamble packets.
+    packet_index = ExpectStandardPreamble(packet_index);
 
     // First packet needs to emit new interning entries
-    auto* e_packet1 = producer_client()->GetFinalizedPacket(3 + (6 * i));
+    auto* e_packet1 = producer_client()->GetFinalizedPacket(packet_index++);
     ExpectTraceEvent(e_packet1, /*category_iid=*/1u, /*name_iid=*/1u,
                      TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1237,7 +1255,7 @@
     ExpectDebugAnnotationNames(e_packet1, {{1u, "arg1"}});
 
     // Second packet refers to the interning entries from packet 1.
-    auto* e_packet2 = producer_client()->GetFinalizedPacket(4 + (6 * i));
+    auto* e_packet2 = producer_client()->GetFinalizedPacket(packet_index++);
     ExpectTraceEvent(e_packet2, /*category_iid=*/1u, /*name_iid=*/1u,
                      TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1251,7 +1269,7 @@
     ExpectDebugAnnotationNames(e_packet2, {});
 
     // Third packet uses different names, so emits new entries.
-    auto* e_packet3 = producer_client()->GetFinalizedPacket(5 + (6 * i));
+    auto* e_packet3 = producer_client()->GetFinalizedPacket(packet_index++);
     ExpectTraceEvent(e_packet3, /*category_iid=*/2u, /*name_iid=*/2u,
                      TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1274,12 +1292,11 @@
   CreateTraceEventDataSource(/* privacy_filtering_enabled =*/true);
   TRACE_EVENT_BEGIN0(kCategoryGroup, "bar");
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble(
-      /*start_packet_index=*/0,
-      /*privacy_filtering_enabled=*/true);  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble(
+      /*start_packet_index=*/0u,
+      /*privacy_filtering_enabled=*/true);
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_BEGIN);
 
@@ -1293,12 +1310,11 @@
   TRACE_EVENT_INSTANT2(kCategoryGroup, "bar", TRACE_EVENT_SCOPE_THREAD, "foo",
                        42, "bar", "string_val");
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble(
+  size_t packet_index = ExpectStandardPreamble(
       /*start_packet_index=*/0u,
-      /*privacy_filtering_enabled=*/true);  // 3 preamble packets.
+      /*privacy_filtering_enabled=*/true);
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1320,12 +1336,11 @@
                            TRACE_EVENT_FLAG_JAVA_STRING_LITERALS,
                        "arg1_name", "arg1_val", "arg2_name", "arg2_val");
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 5u);
-  ExpectStandardPreamble(
+  size_t packet_index = ExpectStandardPreamble(
       /*start_packet_index=*/0u,
-      /*privacy_filtering_enabled=*/true);  // 3 preamble packets.
+      /*privacy_filtering_enabled=*/true);
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1336,7 +1351,7 @@
   ExpectEventNames(e_packet, {{1u, "PRIVACY_FILTERED"}});
   ExpectDebugAnnotationNames(e_packet, {});
 
-  e_packet = producer_client()->GetFinalizedPacket(4);
+  e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/2u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1421,11 +1436,10 @@
   TRACE_EVENT_INSTANT1("cat1", "e1", TRACE_EVENT_SCOPE_THREAD, "arg1", 2);
   TRACE_EVENT_INSTANT1("cat2", "e2", TRACE_EVENT_SCOPE_THREAD, "arg2", 1);
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 6u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
   // First packet needs to emit new interning entries
-  auto* e_packet1 = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet1 = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet1, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1439,7 +1453,7 @@
   ExpectDebugAnnotationNames(e_packet1, {{1u, "arg1"}});
 
   // Second packet reemits the entries the same way.
-  auto* e_packet2 = producer_client()->GetFinalizedPacket(4);
+  auto* e_packet2 = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet2, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1453,7 +1467,7 @@
   ExpectDebugAnnotationNames(e_packet1, {{1u, "arg1"}});
 
   // Third packet emits entries with the same IDs but different strings.
-  auto* e_packet3 = producer_client()->GetFinalizedPacket(5);
+  auto* e_packet3 = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet3, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1563,10 +1577,9 @@
 
   EXPECT_TRUE(begin_called);
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_BEGIN);
 
@@ -1588,10 +1601,9 @@
 
   EXPECT_TRUE(end_called);
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/0u, /*name_iid=*/0u,
                    TRACE_EVENT_PHASE_END);
 
@@ -1609,10 +1621,9 @@
     ctx.event()->set_log_message()->set_body_iid(84);
   });
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 5u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_BEGIN);
 
@@ -1621,7 +1632,7 @@
   ASSERT_TRUE(e_packet->track_event().has_log_message());
   EXPECT_EQ(e_packet->track_event().log_message().body_iid(), 42u);
 
-  e_packet = producer_client()->GetFinalizedPacket(4);
+  e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/0u, /*name_iid=*/0u,
                    TRACE_EVENT_PHASE_END);
 
@@ -1637,10 +1648,9 @@
                         ctx.event()->set_log_message()->set_body_iid(42);
                       });
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_INSTANT, TRACE_EVENT_SCOPE_THREAD);
 
@@ -1661,10 +1671,9 @@
       ctx.event()->set_log_message()->set_body_iid(42);
     });
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 5u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_BEGIN);
 
@@ -1673,7 +1682,7 @@
   ASSERT_TRUE(e_packet->track_event().has_log_message());
   EXPECT_EQ(e_packet->track_event().log_message().body_iid(), 42u);
 
-  e_packet = producer_client()->GetFinalizedPacket(4);
+  e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/0u, /*name_iid=*/0u,
                    TRACE_EVENT_PHASE_END);
 
@@ -1691,10 +1700,9 @@
     });
   }
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 5u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_BEGIN);
 
@@ -1703,7 +1711,7 @@
   ASSERT_TRUE(e_packet->track_event().has_log_message());
   EXPECT_EQ(e_packet->track_event().log_message().body_iid(), 42u);
 
-  e_packet = producer_client()->GetFinalizedPacket(4);
+  e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/0u, /*name_iid=*/0u,
                    TRACE_EVENT_PHASE_END);
 
@@ -1723,11 +1731,10 @@
     });
   }
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 7u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
   // The first TRACE_EVENT begin.
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_BEGIN);
 
@@ -1737,21 +1744,21 @@
   EXPECT_EQ(e_packet->track_event().log_message().body_iid(), 42u);
 
   // The second TRACE_EVENT begin.
-  e_packet = producer_client()->GetFinalizedPacket(4);
+  e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/1u, /*name_iid=*/1u,
                    TRACE_EVENT_PHASE_BEGIN);
   ASSERT_TRUE(e_packet->track_event().has_log_message());
   EXPECT_EQ(e_packet->track_event().log_message().body_iid(), 43u);
 
   // The second TRACE_EVENT end.
-  e_packet = producer_client()->GetFinalizedPacket(5);
+  e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/0u, /*name_iid=*/0u,
                    TRACE_EVENT_PHASE_END);
 
   EXPECT_FALSE(e_packet->track_event().has_log_message());
 
   // The first TRACE_EVENT end.
-  e_packet = producer_client()->GetFinalizedPacket(6);
+  e_packet = producer_client()->GetFinalizedPacket(packet_index++);
   ExpectTraceEvent(e_packet, /*category_iid=*/0u, /*name_iid=*/0u,
                    TRACE_EVENT_PHASE_END);
   EXPECT_FALSE(e_packet->track_event().has_log_message());
@@ -1767,10 +1774,9 @@
 
   UMA_HISTOGRAM_BOOLEAN("Foo.Bar", true);
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
 
   ExpectEventCategories(e_packet,
                         {{1u, TRACE_DISABLED_BY_DEFAULT("histogram_samples")}});
@@ -1796,10 +1802,9 @@
 
   base::RecordAction(base::UserMetricsAction("Test_Action"));
 
-  EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 4u);
-  ExpectStandardPreamble();  // 3 preamble packets.
+  size_t packet_index = ExpectStandardPreamble();
 
-  auto* e_packet = producer_client()->GetFinalizedPacket(3);
+  auto* e_packet = producer_client()->GetFinalizedPacket(packet_index++);
 
   ExpectEventCategories(
       e_packet, {{1u, TRACE_DISABLED_BY_DEFAULT("user_action_samples")}});
diff --git a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc
index b61f26d1..8c682d0 100644
--- a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc
+++ b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc
@@ -37,6 +37,7 @@
 using TraceLog = base::trace_event::TraceLog;
 using perfetto::protos::pbzero::ChromeThreadDescriptor;
 using perfetto::protos::pbzero::ClockSnapshot;
+using perfetto::protos::pbzero::CounterDescriptor;
 using perfetto::protos::pbzero::ThreadDescriptor;
 using perfetto::protos::pbzero::TracePacket;
 using perfetto::protos::pbzero::TracePacketDefaults;
@@ -56,12 +57,6 @@
 constexpr uint32_t kClockIdAbsolute = 64;
 constexpr uint32_t kClockIdIncremental = 65;
 
-base::ThreadTicks ThreadNow() {
-  return base::ThreadTicks::IsSupported()
-             ? base::subtle::ThreadTicksNowIgnoringOverride()
-             : base::ThreadTicks();
-}
-
 // Names of events that should be converted into a TaskExecution event.
 const char* kTaskExecutionEventCategory = "toplevel";
 const char* kTaskExecutionEventNames[3] = {"ThreadControllerImpl::RunTask",
@@ -390,32 +385,32 @@
     }
   }
 
-  if (!trace_event->thread_timestamp().is_null()) {
-    // Thread timestamps are never user-provided, but COMPLETE events may get
-    // reordered, so we can still observe timestamps that are further in the
-    // past. Emit those as absolute timestamps, since we don't support
-    // negative deltas.
-    if (last_thread_time_ > trace_event->thread_timestamp()) {
-      track_event->set_thread_time_absolute_us(
-          trace_event->thread_timestamp().since_origin().InMicroseconds());
-    } else {
-      track_event->set_thread_time_delta_us(
-          (trace_event->thread_timestamp() - last_thread_time_)
-              .InMicroseconds());
-      last_thread_time_ = trace_event->thread_timestamp();
-    }
-  }
+  bool has_thread_time = !trace_event->thread_timestamp().is_null();
+  bool has_instruction_count =
+      !trace_event->thread_instruction_count().is_null();
 
-  if (!trace_event->thread_instruction_count().is_null()) {
-    // Thread instruction counts are never user-provided, but COMPLETE events
-    // may get reordered, so we can still observe counts that are lower. Emit
-    // those as absolute counts, since we don't support negative deltas.
-    if (last_thread_instruction_count_.ToInternalValue() >
-        trace_event->thread_instruction_count().ToInternalValue()) {
-      track_event->set_thread_instruction_count_absolute(
-          trace_event->thread_instruction_count().ToInternalValue());
-    } else {
-      track_event->set_thread_instruction_count_delta(
+  // We always snapshot the thread timestamp when we snapshot instruction
+  // count. If we didn't do this, we'd have to make sure to override the
+  // value of extra_counter_track_uuids.
+  DCHECK(has_thread_time || !has_instruction_count);
+
+  if (has_thread_time) {
+    // Thread timestamps are never user-provided, and since we split COMPLETE
+    // events into BEGIN+END event pairs, they should not appear out of order.
+    DCHECK(trace_event->thread_timestamp() >= last_thread_time_);
+
+    track_event->add_extra_counter_values(
+        (trace_event->thread_timestamp() - last_thread_time_).InMicroseconds());
+    last_thread_time_ = trace_event->thread_timestamp();
+
+    if (has_instruction_count) {
+      // Thread instruction counts are never user-provided, and since we split
+      // COMPLETE events into BEGIN+END event pairs, they should not appear out
+      // of order.
+      DCHECK(trace_event->thread_instruction_count().ToInternalValue() >=
+             last_thread_instruction_count_.ToInternalValue());
+
+      track_event->add_extra_counter_values(
           (trace_event->thread_instruction_count() -
            last_thread_instruction_count_)
               .ToInternalValue());
@@ -695,18 +690,17 @@
 void TrackEventThreadLocalEventSink::OnThreadNameChanged(const char* name) {
   if (thread_id_ != static_cast<int>(base::PlatformThread::CurrentId()))
     return;
-  auto trace_packet = trace_writer_->NewTracePacket();
-  EmitTrackDescriptor(&trace_packet, nullptr, TRACE_TIME_TICKS_NOW(), name);
+  EmitThreadTrackDescriptor(nullptr, TRACE_TIME_TICKS_NOW(), name);
 }
 
-void TrackEventThreadLocalEventSink::EmitTrackDescriptor(
-    protozero::MessageHandle<TracePacket>* trace_packet,
+void TrackEventThreadLocalEventSink::EmitThreadTrackDescriptor(
     base::trace_event::TraceEvent* trace_event,
     base::TimeTicks timestamp,
     const char* maybe_new_name) {
-  SetPacketTimestamp(trace_packet, timestamp);
+  auto packet = trace_writer_->NewTracePacket();
+  SetPacketTimestamp(&packet, timestamp);
 
-  TrackDescriptor* track_descriptor = (*trace_packet)->set_track_descriptor();
+  TrackDescriptor* track_descriptor = packet->set_track_descriptor();
   // TODO(eseckler): Call ThreadTrack::Current() instead once the client lib
   // supports Chrome's tids.
   auto thread_track = perfetto::ThreadTrack::ForThread(thread_id_);
@@ -716,7 +710,7 @@
   // from Chrome, and supports pivacy filtering, and we moved off reference_*
   // fields in ThreadDescriptor.
   track_descriptor->set_uuid(thread_track.uuid);
-  PERFETTO_DCHECK(thread_track.parent_uuid);
+  DCHECK(thread_track.parent_uuid);
   track_descriptor->set_parent_uuid(thread_track.parent_uuid);
 
   ThreadDescriptor* thread = track_descriptor->set_thread();
@@ -736,42 +730,49 @@
     thread->set_thread_name(thread_name_);
   }
 
-  // TODO(eseckler): Switch to a more generic encoding format for counters and
-  // move them out of ThreadDescriptor.
-  if (!trace_event || trace_event->thread_timestamp().is_null()) {
-    last_thread_time_ = ThreadNow();
-  } else {
-    // Thread timestamp is never user-provided.
-    DCHECK_LE(trace_event->thread_timestamp(), ThreadNow());
-    last_thread_time_ = trace_event->thread_timestamp();
-  }
-  thread->set_reference_thread_time_us(
-      last_thread_time_.since_origin().InMicroseconds());
-
-  if (base::trace_event::ThreadInstructionCount::IsSupported()) {
-    if (!trace_event || trace_event->thread_instruction_count().is_null()) {
-      last_thread_instruction_count_ =
-          base::trace_event::ThreadInstructionCount::Now();
-    } else {
-      // Thread instruction count is never user-provided.
-      DCHECK_LE(
-          trace_event->thread_instruction_count().ToInternalValue(),
-          base::trace_event::ThreadInstructionCount::Now().ToInternalValue());
-      last_thread_instruction_count_ = trace_event->thread_instruction_count();
-    }
-    thread->set_reference_thread_instruction_count(
-        last_thread_instruction_count_.ToInternalValue());
-  }
-
   ChromeThreadDescriptor* chrome_thread = track_descriptor->set_chrome_thread();
   chrome_thread->set_thread_type(thread_type_);
 
   // TODO(eseckler): Fill in remaining fields in ChromeThreadDescriptor.
 }
 
+void TrackEventThreadLocalEventSink::EmitCounterTrackDescriptor(
+    base::TimeTicks timestamp,
+    uint64_t thread_track_uuid,
+    uint64_t counter_track_uuid_bit,
+    CounterDescriptor::BuiltinCounterType counter_type,
+    uint64_t unit_multiplier) {
+  auto packet = trace_writer_->NewTracePacket();
+  SetPacketTimestamp(&packet, timestamp);
+
+  TrackDescriptor* track_descriptor = packet->set_track_descriptor();
+
+  // TODO(eseckler): Switch to client library support for CounterTrack uuid
+  // calculation once available.
+  track_descriptor->set_uuid(thread_track_uuid ^ counter_track_uuid_bit);
+  track_descriptor->set_parent_uuid(thread_track_uuid);
+
+  CounterDescriptor* counter = track_descriptor->set_counter();
+  if (counter_type != CounterDescriptor::COUNTER_UNSPECIFIED) {
+    counter->set_type(CounterDescriptor::COUNTER_THREAD_TIME_NS);
+  }
+  if (unit_multiplier) {
+    counter->set_unit_multiplier(1000u);
+  }
+  counter->set_is_incremental(true);
+}
+
 void TrackEventThreadLocalEventSink::DoResetIncrementalState(
     base::trace_event::TraceEvent* trace_event,
     bool explicit_timestamp) {
+  // Bits xor-ed into the track uuid of a thread track to make the track uuid of
+  // a thread time / instruction count track. These bits are chosen from the
+  // upper end of the uint64_t bytes, because the tid of the thread is hashed
+  // into the least significant 32 bits of the uuid.
+  constexpr uint64_t kThreadTimeTrackUuidBit = static_cast<uint64_t>(1u) << 32;
+  constexpr uint64_t kThreadInstructionCountTrackUuidBit =
+      static_cast<uint64_t>(1u) << 33;
+
   interned_event_categories_.ResetEmittedState();
   interned_event_names_.ResetEmittedState();
   interned_annotation_names_.ResetEmittedState();
@@ -787,6 +788,11 @@
   }
   last_timestamp_ = timestamp;
 
+  // TODO(eseckler): Call ThreadTrack::Current() instead once the client lib
+  // supports Chrome's tids.
+  uint64_t thread_track_uuid =
+      perfetto::ThreadTrack::ForThread(thread_id_).uuid;
+
   {
     // Emit a new clock snapshot with this timestamp, and also set the
     // |incremental_state_cleared| flag and defaults.
@@ -796,10 +802,16 @@
     TracePacketDefaults* tp_defaults = packet->set_trace_packet_defaults();
     tp_defaults->set_timestamp_clock_id(kClockIdIncremental);
     TrackEventDefaults* te_defaults = tp_defaults->set_track_event_defaults();
-    // TODO(eseckler): Call ThreadTrack::Current() instead once the client lib
-    // supports Chrome's tids.
-    te_defaults->set_track_uuid(
-        perfetto::ThreadTrack::ForThread(thread_id_).uuid);
+
+    // Default to thread track, with counter values for thread time and
+    // instruction count, if supported.
+    te_defaults->set_track_uuid(thread_track_uuid);
+    te_defaults->add_extra_counter_track_uuids(thread_track_uuid ^
+                                               kThreadTimeTrackUuidBit);
+    if (base::trace_event::ThreadInstructionCount::IsSupported()) {
+      te_defaults->add_extra_counter_track_uuids(
+          thread_track_uuid ^ kThreadInstructionCountTrackUuidBit);
+    }
 
     ClockSnapshot* clocks = packet->set_clock_snapshot();
     // Always reference the boottime timestamps to help trace processor
@@ -829,9 +841,23 @@
     clock_incremental->set_is_incremental(true);
   }
 
-  // Emit a new track descriptor in another packet.
-  auto packet = trace_writer_->NewTracePacket();
-  EmitTrackDescriptor(&packet, trace_event, timestamp);
+  // Emit new track descriptors for the thread and its counters in separate
+  // packets.
+  EmitThreadTrackDescriptor(trace_event, timestamp);
+  if (base::ThreadTicks::IsSupported()) {
+    EmitCounterTrackDescriptor(
+        timestamp, thread_track_uuid, kThreadTimeTrackUuidBit,
+        CounterDescriptor::COUNTER_THREAD_TIME_NS, 1000u);
+  }
+  if (base::trace_event::ThreadInstructionCount::IsSupported()) {
+    EmitCounterTrackDescriptor(
+        timestamp, thread_track_uuid, kThreadInstructionCountTrackUuidBit,
+        CounterDescriptor::COUNTER_THREAD_INSTRUCTION_COUNT);
+  }
+
+  // The first set of counter values should be absolute.
+  last_thread_time_ = base::ThreadTicks();
+  last_thread_instruction_count_ = base::trace_event::ThreadInstructionCount();
 
   reset_incremental_state_ = false;
 }
diff --git a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h
index 559ec4a..3c335ca 100644
--- a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h
+++ b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h
@@ -22,6 +22,7 @@
 #include "third_party/perfetto/include/perfetto/tracing/event_context.h"
 #include "third_party/perfetto/protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_thread_descriptor.pbzero.h"
+#include "third_party/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pbzero.h"
 #include "third_party/perfetto/protos/perfetto/trace/track_event/track_event.pbzero.h"
 
 namespace tracing {
@@ -125,12 +126,16 @@
  private:
   static constexpr size_t kMaxCompleteEventDepth = 30;
 
-  void EmitTrackDescriptor(
-      protozero::MessageHandle<perfetto::protos::pbzero::TracePacket>*
-          trace_packet,
-      base::trace_event::TraceEvent* trace_event,
+  void EmitThreadTrackDescriptor(base::trace_event::TraceEvent* trace_event,
+                                 base::TimeTicks timestamp,
+                                 const char* maybe_new_name = nullptr);
+  void EmitCounterTrackDescriptor(
       base::TimeTicks timestamp,
-      const char* maybe_new_name = nullptr);
+      uint64_t thread_track_uuid,
+      uint64_t counter_track_uuid_bit,
+      perfetto::protos::pbzero::CounterDescriptor::BuiltinCounterType
+          counter_type,
+      uint64_t unit_multiplier = 0u);
   void DoResetIncrementalState(base::trace_event::TraceEvent* trace_event,
                                bool explicit_timestamp);
   void SetPacketTimestamp(
diff --git a/services/tracing/public/mojom/perfetto_service.mojom b/services/tracing/public/mojom/perfetto_service.mojom
index 3a8e2e5..7dc5b024 100644
--- a/services/tracing/public/mojom/perfetto_service.mojom
+++ b/services/tracing/public/mojom/perfetto_service.mojom
@@ -170,10 +170,13 @@
   array<string> producer_name_filter;
 };
 
+// Allows to disable the built-in data sources implicitly enabled in the tracing service.
+// See comments in third_party/perfetto/protos/perfetto/config/trace_config.proto.
 struct PerfettoBuiltinDataSource {
   bool disable_clock_snapshotting;
   bool disable_trace_config;
   bool disable_system_info;
+  bool disable_service_events;
 };
 
 struct IncrementalStateConfig {
diff --git a/services/tracing/public/mojom/trace_config_mojom_traits.cc b/services/tracing/public/mojom/trace_config_mojom_traits.cc
index 15497d0..49ea3c5 100644
--- a/services/tracing/public/mojom/trace_config_mojom_traits.cc
+++ b/services/tracing/public/mojom/trace_config_mojom_traits.cc
@@ -56,6 +56,7 @@
   out->set_disable_clock_snapshotting(data.disable_clock_snapshotting());
   out->set_disable_trace_config(data.disable_trace_config());
   out->set_disable_system_info(data.disable_system_info());
+  out->set_disable_service_events(data.disable_service_events());
   return true;
 }
 
diff --git a/services/tracing/public/mojom/trace_config_mojom_traits.h b/services/tracing/public/mojom/trace_config_mojom_traits.h
index 702c1a6d..05b2aae 100644
--- a/services/tracing/public/mojom/trace_config_mojom_traits.h
+++ b/services/tracing/public/mojom/trace_config_mojom_traits.h
@@ -107,6 +107,11 @@
     return src.disable_system_info();
   }
 
+  static bool disable_service_events(
+      const perfetto::TraceConfig::BuiltinDataSource& src) {
+    return src.disable_service_events();
+  }
+
   static bool Read(tracing::mojom::PerfettoBuiltinDataSourceDataView data,
                    perfetto::TraceConfig::BuiltinDataSource* out);
 };
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 41bc4c3..8e55bf2 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -1731,7 +1731,7 @@
   "upload_trace_processor": {
     "label": "//tools/perf/core/perfetto_binary_roller:upload_trace_processor",
     "type": "generated_script",
-    "script": "upload_trace_processor",
+    "script": "bin/upload_trace_processor",
   },
   "url_unittests": {
     "label": "//url:url_unittests",
diff --git a/third_party/accessibility_test_framework/OWNERS b/third_party/accessibility_test_framework/OWNERS
index df1ed5b..8fd43ad6 100644
--- a/third_party/accessibility_test_framework/OWNERS
+++ b/third_party/accessibility_test_framework/OWNERS
@@ -1,5 +1,5 @@
-jbudorick@chromium.org
-mikecase@chromium.org
-yolandyan@chromium.org
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
 
 # COMPONENT: Test>Android
diff --git a/third_party/android_build_tools/aapt2/OWNERS b/third_party/android_build_tools/aapt2/OWNERS
index 320b48b..0deed39 100644
--- a/third_party/android_build_tools/aapt2/OWNERS
+++ b/third_party/android_build_tools/aapt2/OWNERS
@@ -1,5 +1,4 @@
 digit@chromium.org
 agrieve@chromium.org
-jbudorick@chromium.org
 mheikal@chromium.org
 # COMPONENT: Build
diff --git a/third_party/android_build_tools/art/OWNERS b/third_party/android_build_tools/art/OWNERS
index 15d768d1..b191b0fb 100644
--- a/third_party/android_build_tools/art/OWNERS
+++ b/third_party/android_build_tools/art/OWNERS
@@ -1,5 +1,4 @@
 agrieve@chromium.org
-jbudorick@chromium.org
 lizeb@chromium.org
 mheikal@chomrium.org
 
diff --git a/third_party/android_build_tools/bundletool/OWNERS b/third_party/android_build_tools/bundletool/OWNERS
index f630763..fc4e0a5 100644
--- a/third_party/android_build_tools/bundletool/OWNERS
+++ b/third_party/android_build_tools/bundletool/OWNERS
@@ -1,4 +1,3 @@
 digit@chromium.org
 agrieve@chromium.org
-jbudorick@chromium.org
 wnwen@chromium.org
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index 6d63b7c..4e213e8 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -25,15 +25,13 @@
 java_group("android_support_v7_appcompat_java") {
   deps = [
     ":android_support_v4_java",
-    ":com_android_support_animated_vector_drawable_java",
-    ":com_android_support_appcompat_v7_java",
-    ":com_android_support_support_vector_drawable_java",
+    ":androidx_appcompat_appcompat_java",
+    ":androidx_vectordrawable_vectordrawable_animated_java",
+    ":androidx_vectordrawable_vectordrawable_java",
+    ":com_android_support_appcompat_v7_java__unpack_aar",
   ]
-  input_jars_paths = [
-    "${target_out_dir}/com_android_support_appcompat_v7_java_orig/classes.jar",
-    "${target_out_dir}/com_android_support_animated_vector_drawable_java_orig/classes.jar",
-    "${target_out_dir}/com_android_support_support_vector_drawable_java_orig/classes.jar",
-  ]
+  input_jars_paths =
+      [ "${target_out_dir}/com_android_support_appcompat_v7_java/classes.jar" ]
 }
 
 java_group("android_arch_lifecycle_common_java") {
@@ -51,22 +49,14 @@
   ]
 }
 
-java_group("com_android_support_animated_vector_drawable_java") {
-  deps = [
-    ":${target_name}_orig__unpack_aar",
-    ":androidx_vectordrawable_vectordrawable_animated_java",
-  ]
-  input_jars_paths = [ "${target_out_dir}/com_android_support_animated_vector_drawable_java_orig/classes.jar" ]
+# TODO (bjoyce): Remove when downstream bots can call this directly.
+java_group("com_android_support_animated_vector_drawable_java_temp") {
+  deps = [ ":com_android_support_animated_vector_drawable_java" ]
 }
 
-java_group("com_android_support_appcompat_v7_java") {
-  deps = [
-    ":${target_name}_orig__unpack_aar",
-    ":androidx_appcompat_appcompat_java",
-  ]
-  input_jars_paths = [
-    "${target_out_dir}/com_android_support_appcompat_v7_java_orig/classes.jar",
-  ]
+# TODO (bjoyce): Remove when downstream bots can call this directly.
+java_group("com_android_support_appcompat_v7_java_temp") {
+  deps = [ ":com_android_support_appcompat_v7_java" ]
 }
 
 java_group("com_android_support_cardview_v7_java") {
@@ -188,12 +178,9 @@
   ]
 }
 
-java_group("com_android_support_support_vector_drawable_java") {
-  deps = [
-    ":${target_name}_orig__unpack_aar",
-    ":androidx_vectordrawable_vectordrawable_java",
-  ]
-  input_jars_paths = [ "${target_out_dir}/com_android_support_support_vector_drawable_java_orig/classes.jar" ]
+# TODO (bjoyce): Remove when downstream bots can call this directly.
+java_group("com_android_support_support_vector_drawable_java_temp") {
+  deps = [ ":com_android_support_support_vector_drawable_java" ]
 }
 
 java_group("com_android_support_versionedparcelable_java") {
@@ -779,18 +766,18 @@
 }
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-android_aar_prebuilt("com_android_support_appcompat_v7_java_orig") {
+android_aar_prebuilt("com_android_support_appcompat_v7_java") {
   aar_path = "libs/com_android_support_appcompat_v7/appcompat-v7-28.0.0.aar"
   info_path = "libs/com_android_support_appcompat_v7/com_android_support_appcompat_v7.info"
   deps = [
-    ":com_android_support_animated_vector_drawable_java_orig",
+    ":com_android_support_animated_vector_drawable_java",
     ":com_android_support_collections_java_orig",
     ":com_android_support_cursoradapter_java",
     ":com_android_support_support_annotations_java_orig",
     ":com_android_support_support_compat_java_orig",
     ":com_android_support_support_core_utils_java_orig",
     ":com_android_support_support_fragment_java_orig",
-    ":com_android_support_support_vector_drawable_java_orig",
+    ":com_android_support_support_vector_drawable_java",
   ]
   skip_jetify = true
   jar_excluded_patterns = [ "androidx/appcompat/R*" ]
@@ -857,7 +844,7 @@
   aar_path = "libs/com_android_support_design/design-28.0.0.aar"
   info_path = "libs/com_android_support_design/com_android_support_design.info"
   deps = [
-    ":com_android_support_appcompat_v7_java_orig",
+    ":com_android_support_appcompat_v7_java",
     ":com_android_support_cardview_v7_java_orig",
     ":com_android_support_recyclerview_v7_java_orig",
     ":com_android_support_support_annotations_java_orig",
@@ -915,7 +902,7 @@
   aar_path = "libs/com_android_support_mediarouter_v7/mediarouter-v7-28.0.0.aar"
   info_path = "libs/com_android_support_mediarouter_v7/com_android_support_mediarouter_v7.info"
   deps = [
-    ":com_android_support_appcompat_v7_java_orig",
+    ":com_android_support_appcompat_v7_java",
     ":com_android_support_palette_v7_java",
     ":com_android_support_recyclerview_v7_java_orig",
     ":com_android_support_support_media_compat_java_orig",
@@ -939,7 +926,7 @@
   aar_path = "libs/com_android_support_preference_leanback_v17/preference-leanback-v17-28.0.0.aar"
   info_path = "libs/com_android_support_preference_leanback_v17/com_android_support_preference_leanback_v17.info"
   deps = [
-    ":com_android_support_appcompat_v7_java_orig",
+    ":com_android_support_appcompat_v7_java",
     ":com_android_support_collections_java_orig",
     ":com_android_support_leanback_v17_java",
     ":com_android_support_preference_v14_java",
@@ -955,7 +942,7 @@
   aar_path = "libs/com_android_support_preference_v7/preference-v7-28.0.0.aar"
   info_path = "libs/com_android_support_preference_v7/com_android_support_preference_v7.info"
   deps = [
-    ":com_android_support_appcompat_v7_java_orig",
+    ":com_android_support_appcompat_v7_java",
     ":com_android_support_collections_java_orig",
     ":com_android_support_recyclerview_v7_java_orig",
     ":com_android_support_support_compat_java_orig",
@@ -1901,7 +1888,7 @@
 }
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-android_aar_prebuilt("com_android_support_animated_vector_drawable_java_orig") {
+android_aar_prebuilt("com_android_support_animated_vector_drawable_java") {
   aar_path = "libs/com_android_support_animated_vector_drawable/animated-vector-drawable-28.0.0.aar"
   info_path = "libs/com_android_support_animated_vector_drawable/com_android_support_animated_vector_drawable.info"
 
@@ -1910,7 +1897,7 @@
   visibility = [ ":*" ]
   deps = [
     ":com_android_support_support_core_ui_java_orig",
-    ":com_android_support_support_vector_drawable_java_orig",
+    ":com_android_support_support_vector_drawable_java",
   ]
   skip_jetify = true
 }
@@ -2050,7 +2037,7 @@
 }
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-android_aar_prebuilt("com_android_support_support_vector_drawable_java_orig") {
+android_aar_prebuilt("com_android_support_support_vector_drawable_java") {
   aar_path = "libs/com_android_support_support_vector_drawable/support-vector-drawable-28.0.0.aar"
   info_path = "libs/com_android_support_support_vector_drawable/com_android_support_support_vector_drawable.info"
 
diff --git a/third_party/android_deps/OWNERS b/third_party/android_deps/OWNERS
index ad0973f..9fdc4d7 100644
--- a/third_party/android_deps/OWNERS
+++ b/third_party/android_deps/OWNERS
@@ -1,5 +1,4 @@
 agrieve@chromium.org
-jbudorick@chromium.org
 mheikal@chromium.org
 smaier@chromium.org
 wnwen@chromium.org
diff --git a/third_party/android_sdk/OWNERS b/third_party/android_sdk/OWNERS
index 96d7f9d..ca545dc 100644
--- a/third_party/android_sdk/OWNERS
+++ b/third_party/android_sdk/OWNERS
@@ -1,5 +1,4 @@
 agrieve@chromium.org
-jbudorick@chromium.org
 michaelbai@chromium.org
 primiano@chromium.org
 rmcilroy@chromium.org
diff --git a/third_party/android_support_test_runner/OWNERS b/third_party/android_support_test_runner/OWNERS
index df1ed5b..8fd43ad6 100644
--- a/third_party/android_support_test_runner/OWNERS
+++ b/third_party/android_support_test_runner/OWNERS
@@ -1,5 +1,5 @@
-jbudorick@chromium.org
-mikecase@chromium.org
-yolandyan@chromium.org
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
 
 # COMPONENT: Test>Android
diff --git a/third_party/bazel/OWNERS b/third_party/bazel/OWNERS
index aed8b1a..d1f9484 100644
--- a/third_party/bazel/OWNERS
+++ b/third_party/bazel/OWNERS
@@ -1,4 +1,4 @@
 agrieve@chromium.org
-jbudorick@chromium.org
+wnwen@chromium.org
 
 # COMPONENT: Build
diff --git a/third_party/blink/public/common/input/web_input_event_attribution.h b/third_party/blink/public/common/input/web_input_event_attribution.h
index 4b0db30b..45e60cf 100644
--- a/third_party/blink/public/common/input/web_input_event_attribution.h
+++ b/third_party/blink/public/common/input/web_input_event_attribution.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_INPUT_WEB_INPUT_EVENT_ATTRIBUTION_H_
 #define THIRD_PARTY_BLINK_PUBLIC_COMMON_INPUT_WEB_INPUT_EVENT_ATTRIBUTION_H_
 
+#include "base/hash/hash.h"
 #include "cc/paint/element_id.h"
 
 namespace blink {
@@ -35,6 +36,14 @@
   Type type() const { return type_; }
   cc::ElementId target_frame_id() const { return target_frame_id_; }
 
+  bool operator==(const WebInputEventAttribution& other) const {
+    return other.type() == type_ && other.target_frame_id() == target_frame_id_;
+  }
+
+  size_t GetHash() const {
+    return base::HashInts(type_, target_frame_id_.GetStableId());
+  }
+
  private:
   Type type_;
   cc::ElementId target_frame_id_;  // Valid if type is kTargetedFrame.
diff --git a/third_party/blink/public/devtools_protocol/OWNERS b/third_party/blink/public/devtools_protocol/OWNERS
index ddbdc49..0fc2f5f 100644
--- a/third_party/blink/public/devtools_protocol/OWNERS
+++ b/third_party/blink/public/devtools_protocol/OWNERS
@@ -3,4 +3,4 @@
 dgozman@chromium.org
 pfeldman@chromium.org
 
-# COMPONENT: Platform>DevTools
+# COMPONENT: Platform>DevTools>Platform
diff --git a/third_party/blink/public/mojom/fetch/fetch_api_response.mojom b/third_party/blink/public/mojom/fetch/fetch_api_response.mojom
index e8e5fe8..ac25dce 100644
--- a/third_party/blink/public/mojom/fetch/fetch_api_response.mojom
+++ b/third_party/blink/public/mojom/fetch/fetch_api_response.mojom
@@ -7,6 +7,7 @@
 import "mojo/public/mojom/base/time.mojom";
 import "services/network/public/mojom/fetch_api.mojom";
 import "services/network/public/mojom/content_security_policy.mojom";
+import "services/network/public/mojom/cross_origin_embedder_policy.mojom";
 import "third_party/blink/public/mojom/blob/serialized_blob.mojom";
 import "third_party/blink/public/mojom/service_worker/service_worker_error_type.mojom";
 import "url/mojom/url.mojom";
@@ -73,6 +74,10 @@
   // parsed CSP.
   array<network.mojom.ContentSecurityPolicy> content_security_policy;
 
+  // The parsed representation of Cross-Origin-Embedder-Policy and
+  // Cross-Origin-Embedder-Policy-Report-Only headers.
+  network.mojom.CrossOriginEmbedderPolicy cross_origin_embedder_policy;
+
   // True if the response was loaded with a Request where the credentials mode
   // would potentially send cookies to the server:
   //
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index d806a09..5132b21 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -2541,6 +2541,27 @@
   kWrongBaselineOfButtonElement = 3201,
   kV8Document_HasTrustToken_Method = 3202,
   kForceLoadAtTop = 3203,
+  kLegacyLayoutByButton = 3204,
+  kLegacyLayoutByDeprecatedFlexBox = 3205,
+  kLegacyLayoutByDetailsMarker = 3206,
+  kLegacyLayoutByEditing = 3207,
+  kLegacyLayoutByFieldSet = 3208,
+  kLegacyLayoutByFileUploadControl = 3209,
+  kLegacyLayoutByFlexBox = 3210,
+  kLegacyLayoutByFrameSet = 3211,
+  kLegacyLayoutByGrid = 3212,
+  kLegacyLayoutByMenuList = 3213,
+  kLegacyLayoutByMultiCol = 3214,
+  kLegacyLayoutByPrinting = 3215,
+  kLegacyLayoutByRuby = 3216,
+  kLegacyLayoutBySVG = 3217,
+  kLegacyLayoutBySlider = 3218,
+  kLegacyLayoutByTable = 3219,
+  kLegacyLayoutByTextCombine = 3220,
+  kLegacyLayoutByTextControl = 3221,
+  kLegacyLayoutByVTTCue = 3222,
+  kLegacyLayoutByWebkitBoxWithoutVerticalLineClamp = 3223,
+  kLegacyLayoutByTableFlexGridBlockInNGFragmentationContext = 3224,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/renderer/BUILD.gn b/third_party/blink/renderer/BUILD.gn
index 6431181..dfe6ef7 100644
--- a/third_party/blink/renderer/BUILD.gn
+++ b/third_party/blink/renderer/BUILD.gn
@@ -116,14 +116,6 @@
       "blink-gc-plugin",
     ]
 
-    # Don't perform checks for GarbageCollectedFinalized.
-    cflags += [
-      "-Xclang",
-      "-plugin-arg-blink-gc-plugin",
-      "-Xclang",
-      "no-gc-finalized",
-    ]
-
     # Disallow members in stack allocated classes.
     cflags += [
       "-Xclang",
@@ -141,14 +133,6 @@
         "dump-graph",
       ]
     }
-    if (blink_gc_plugin_option_warn_unneeded_finalizer) {
-      cflags += [
-        "-Xclang",
-        "-plugin-arg-blink-gc-plugin",
-        "-Xclang",
-        "warn-unneeded-finalizer",
-      ]
-    }
   }
 }
 
diff --git a/third_party/blink/renderer/bindings/core/v8/generated_code_helper.cc b/third_party/blink/renderer/bindings/core/v8/generated_code_helper.cc
index 7dafcca..9cf5083d 100644
--- a/third_party/blink/renderer/bindings/core/v8/generated_code_helper.cc
+++ b/third_party/blink/renderer/bindings/core/v8/generated_code_helper.cc
@@ -266,6 +266,37 @@
   return function_template->GetFunction(script_state->GetContext());
 }
 
+void InstallUnscopablePropertyNames(
+    v8::Isolate* isolate,
+    v8::Local<v8::Context> context,
+    v8::Local<v8::Object> prototype_object,
+    base::span<const char* const> property_name_table) {
+  // 3.6.3. Interface prototype object
+  // https://heycam.github.io/webidl/#interface-prototype-object
+  // step 8. If interface has any member declared with the [Unscopable]
+  //   extended attribute, then:
+  // step 8.1. Let unscopableObject be the result of performing
+  //   ! ObjectCreate(null).
+  v8::Local<v8::Object> unscopable_object =
+      v8::Object::New(isolate, v8::Null(isolate), nullptr, nullptr, 0);
+  for (const char* const property_name : property_name_table) {
+    // step 8.2.2. Perform ! CreateDataProperty(unscopableObject, id, true).
+    unscopable_object
+        ->CreateDataProperty(context, V8AtomicString(isolate, property_name),
+                             v8::True(isolate))
+        .ToChecked();
+  }
+  // step 8.3. Let desc be the PropertyDescriptor{[[Value]]: unscopableObject,
+  //   [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}.
+  // step 8.4. Perform ! DefinePropertyOrThrow(interfaceProtoObj,
+  //   @@unscopables, desc).
+  prototype_object
+      ->DefineOwnProperty(
+          context, v8::Symbol::GetUnscopables(isolate), unscopable_object,
+          static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontEnum))
+      .ToChecked();
+}
+
 v8::Local<v8::Array> EnumerateIndexedProperties(v8::Isolate* isolate,
                                                 uint32_t length) {
   Vector<v8::Local<v8::Value>> elements;
diff --git a/third_party/blink/renderer/bindings/core/v8/generated_code_helper.h b/third_party/blink/renderer/bindings/core/v8/generated_code_helper.h
index 4907fc9..a4f2b0a36 100644
--- a/third_party/blink/renderer/bindings/core/v8/generated_code_helper.h
+++ b/third_party/blink/renderer/bindings/core/v8/generated_code_helper.h
@@ -158,6 +158,12 @@
     int func_length,
     const WrapperTypeInfo* wrapper_type_info);
 
+CORE_EXPORT void InstallUnscopablePropertyNames(
+    v8::Isolate* isolate,
+    v8::Local<v8::Context> context,
+    v8::Local<v8::Object> prototype_object,
+    base::span<const char* const> property_name_table);
+
 CORE_EXPORT v8::Local<v8::Array> EnumerateIndexedProperties(
     v8::Isolate* isolate,
     uint32_t length);
diff --git a/third_party/blink/renderer/bindings/idl_in_core.gni b/third_party/blink/renderer/bindings/idl_in_core.gni
index 81bc16c..acd5a6b 100644
--- a/third_party/blink/renderer/bindings/idl_in_core.gni
+++ b/third_party/blink/renderer/bindings/idl_in_core.gni
@@ -254,6 +254,8 @@
           "//third_party/blink/renderer/core/frame/fragment_directive.idl",
           "//third_party/blink/renderer/core/frame/history.idl",
           "//third_party/blink/renderer/core/frame/intervention_report_body.idl",
+          "//third_party/blink/renderer/core/frame/is_input_pending_options.idl",
+          "//third_party/blink/renderer/core/frame/is_input_pending_options_init.idl",
           "//third_party/blink/renderer/core/frame/location.idl",
           "//third_party/blink/renderer/core/frame/navigator.idl",
           "//third_party/blink/renderer/core/frame/navigator_automation_information.idl",
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py b/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
index d8226fe9..a0722f6 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
@@ -3973,6 +3973,35 @@
     return callback_def_nodes
 
 
+def _make_install_unscopables(cg_context):
+    assert isinstance(cg_context, CodeGenContext)
+
+    unscopables = []
+    is_unscopable = lambda member: "Unscopable" in member.extended_attributes
+    unscopables.extend(filter(is_unscopable, cg_context.class_like.attributes))
+    unscopables.extend(filter(is_unscopable, cg_context.class_like.operations))
+
+    if not unscopables:
+        return None
+
+    return SequenceNode([
+        ListNode([
+            TextNode("// [Unscopable]"),
+            TextNode("static constexpr const char* "
+                     "kUnscopablePropertyNames[] = {"),
+            ListNode([
+                TextNode("\"{}\", ".format(member.identifier))
+                for member in unscopables
+            ]),
+            TextNode("};"),
+        ]),
+        TextNode("bindings::InstallUnscopablePropertyNames"
+                 "(${isolate}, ${v8_context}, ${prototype_object}, "
+                 "kUnscopablePropertyNames);"),
+        EmptyNode(),
+    ])
+
+
 def make_install_interface_template(
         cg_context, function_name, class_name, trampoline_var_name,
         constructor_entries, indexed_and_named_property_install_nodes,
@@ -4244,8 +4273,13 @@
         isinstance(entry, _PropEntryOperationGroup)
         for entry in operation_entries)
 
+    if is_context_dependent:
+        install_unscopables_node = _make_install_unscopables(cg_context)
+    else:
+        install_unscopables_node = None
+
     if not (attribute_entries or constant_entries or exposed_construct_entries
-            or operation_entries):
+            or operation_entries or install_unscopables_node):
         return None, None, None
 
     if is_context_dependent:
@@ -4328,6 +4362,8 @@
         })
     bind_installer_local_vars(body, cg_context)
 
+    body.append(install_unscopables_node)
+
     def group_by_condition(entries):
         unconditional_entries = []
         conditional_to_entries = {}
diff --git a/third_party/blink/renderer/core/core_idl_files.gni b/third_party/blink/renderer/core/core_idl_files.gni
index e06e440..7776072 100644
--- a/third_party/blink/renderer/core/core_idl_files.gni
+++ b/third_party/blink/renderer/core/core_idl_files.gni
@@ -192,6 +192,7 @@
                     "frame/fragment_directive.idl",
                     "frame/history.idl",
                     "frame/intervention_report_body.idl",
+                    "frame/is_input_pending_options.idl",
                     "frame/location.idl",
                     "frame/navigator_ua_data.idl",
                     "frame/report.idl",
@@ -667,6 +668,7 @@
                     "fetch/trust_token.idl",
                     "fileapi/blob_property_bag.idl",
                     "fileapi/file_property_bag.idl",
+                    "frame/is_input_pending_options_init.idl",
                     "frame/navigator_ua_brand_version.idl",
                     "frame/reporting_observer_options.idl",
                     "frame/scroll_into_view_options.idl",
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc b/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
index d47a9f0..eb7c5e57 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
@@ -51,8 +51,8 @@
                                                                    reason)) {
       DCHECK(locked_activatable_ancestor->GetDisplayLockContext());
       DCHECK(locked_activatable_ancestor->GetDisplayLockContext()->IsLocked());
-      if (locked_activatable_ancestor->GetDisplayLockContext()->UpdateForced())
-        break;
+      DCHECK(!locked_activatable_ancestor->GetDisplayLockContext()
+                  ->UpdateForced());
       scoped_forced_update_list_.push_back(
           locked_activatable_ancestor->GetDisplayLockContext()
               ->GetScopedForcedUpdate());
@@ -213,8 +213,7 @@
     if (!ancestor_node)
       continue;
     if (auto* context = ancestor_node->GetDisplayLockContext()) {
-      if (context->UpdateForced())
-        break;
+      DCHECK(!context->UpdateForced());
       scoped_update_forced_list_.push_back(context->GetScopedForcedUpdate());
     }
   }
@@ -223,10 +222,8 @@
 void DisplayLockUtilities::ScopedChainForcedUpdate::
     CreateParentFrameScopeIfNeeded(const Node* node) {
   auto* owner_node = GetFrameOwnerNode(node);
-  if (owner_node) {
-    parent_frame_scope_ =
-        std::make_unique<ScopedChainForcedUpdate>(owner_node, true);
-  }
+  if (owner_node)
+    parent_frame_scope_.reset(new ScopedChainForcedUpdate(owner_node, true));
 }
 
 const Element* DisplayLockUtilities::NearestLockedInclusiveAncestor(
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities.h b/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
index 8a875f8..b00cc39 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
@@ -23,13 +23,27 @@
     DISALLOW_COPY_AND_ASSIGN(ScopedChainForcedUpdate);
 
    public:
+    ~ScopedChainForcedUpdate() = default;
+
+   private:
+    // It is important not to create multiple ScopedChainForcedUpdate scopes.
+    // The following functions update some combination of Style, Layout, Paint
+    // information after forcing the display locks. It should be enough to use
+    // one of the following functions instead of forcing the scope manually.
+    friend void Document::UpdateStyleAndLayoutForNode(
+        const Node* node,
+        DocumentUpdateReason reason);
+    friend void Document::UpdateStyleAndLayoutTreeForNode(const Node*);
+    friend void Document::UpdateStyleAndLayoutTreeForSubtree(const Node* node);
+    friend void Document::EnsurePaintLocationDataValidForNode(
+        const Node* node,
+        DocumentUpdateReason reason);
+
     explicit ScopedChainForcedUpdate(const Node* node,
                                      bool include_self = false);
-    ~ScopedChainForcedUpdate() = default;
 
     void CreateParentFrameScopeIfNeeded(const Node* node);
 
-   private:
     Vector<DisplayLockContext::ScopedForcedUpdate> scoped_update_forced_list_;
     std::unique_ptr<ScopedChainForcedUpdate> parent_frame_scope_;
   };
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 49c51f6..be13a483 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -317,7 +317,7 @@
 
 bool CalculateStyleShouldForceLegacyLayout(const Element& element,
                                            const ComputedStyle& style) {
-  const Document& document = element.GetDocument();
+  Document& document = element.GetDocument();
 
   if (style.Display() == EDisplay::kLayoutCustom ||
       style.Display() == EDisplay::kInlineLayoutCustom)
@@ -326,26 +326,35 @@
   // TODO(layout-dev): Once LayoutNG handles inline content editable, we
   // should get rid of following code fragment.
   if (!RuntimeEnabledFeatures::EditingNGEnabled()) {
-    if (style.UserModify() != EUserModify::kReadOnly || document.InDesignMode())
+    if (style.UserModify() != EUserModify::kReadOnly ||
+        document.InDesignMode()) {
+      UseCounter::Count(document, WebFeature::kLegacyLayoutByEditing);
       return true;
+    }
   }
 
   if (style.IsDeprecatedWebkitBox() &&
       (!style.IsDeprecatedWebkitBoxWithVerticalLineClamp() ||
        !RuntimeEnabledFeatures::BlockFlowHandlesWebkitLineClampEnabled())) {
+    UseCounter::Count(
+        document, WebFeature::kLegacyLayoutByWebkitBoxWithoutVerticalLineClamp);
     return true;
   }
 
   if (!RuntimeEnabledFeatures::LayoutNGBlockFragmentationEnabled()) {
     // Disable NG for the entire subtree if we're establishing a multicol
     // container.
-    if (style.SpecifiesColumns())
+    if (style.SpecifiesColumns()) {
+      UseCounter::Count(document, WebFeature::kLegacyLayoutByMultiCol);
       return true;
+    }
   }
 
   // No printing support in LayoutNG yet.
-  if (document.Printing() && element == document.documentElement())
+  if (document.Printing() && element == document.documentElement()) {
+    UseCounter::Count(document, WebFeature::kLegacyLayoutByPrinting);
     return true;
+  }
 
   // Fall back to legacy layout for frameset documents. The frameset itself (and
   // the frames) can only create legacy layout objects anyway (no NG counterpart
@@ -354,12 +363,16 @@
   // layout (because of the above check), which would re-attach all layout
   // objects, which would cause the frameset to lose state of some sort, leaving
   // everything blank when printed.
-  if (document.IsFrameSet())
+  if (document.IsFrameSet()) {
+    UseCounter::Count(document, WebFeature::kLegacyLayoutByFrameSet);
     return true;
+  }
 
   // 'text-combine-upright' property is not supported yet.
-  if (style.HasTextCombine() && !style.IsHorizontalWritingMode())
+  if (style.HasTextCombine() && !style.IsHorizontalWritingMode()) {
+    UseCounter::Count(document, WebFeature::kLegacyLayoutByTextCombine);
     return true;
+  }
 
   if (style.InsideNGFragmentationContext()) {
     // If we're inside an NG block fragmentation context, all fragmentable boxes
@@ -368,8 +381,13 @@
     // on). Inline display types end up on a line, and are therefore monolithic,
     // so we can allow those.
     if (!style.IsDisplayInlineType()) {
-      if (style.IsDisplayTableType() || style.IsDisplayFlexibleOrGridBox())
+      if (style.IsDisplayTableType() || style.IsDisplayFlexibleOrGridBox()) {
+        UseCounter::Count(
+            document,
+            WebFeature::
+                kLegacyLayoutByTableFlexGridBlockInNGFragmentationContext);
         return true;
+      }
     }
   }
 
@@ -4182,8 +4200,8 @@
       frame_owner_element->contentDocument()->UnloadStarted())
     return;
 
-  DisplayLockUtilities::ScopedChainForcedUpdate scoped_update_forced(this);
-  GetDocument().UpdateStyleAndLayoutTree();
+  // Ensure we have clean style (including forced display locks).
+  GetDocument().UpdateStyleAndLayoutTreeForNode(this);
 
   // https://html.spec.whatwg.org/C/#focusing-steps
   //
diff --git a/third_party/blink/renderer/core/dom/slot_assignment.cc b/third_party/blink/renderer/core/dom/slot_assignment.cc
index 400b7ec9..404ff11 100644
--- a/third_party/blink/renderer/core/dom/slot_assignment.cc
+++ b/third_party/blink/renderer/core/dom/slot_assignment.cc
@@ -260,13 +260,18 @@
         FindSlotByName(HTMLSlotElement::UserAgentCustomAssignSlotName());
   }
 
+  bool is_manual_slot_assignment = owner_->IsManualSlotting();
+  // Replaces candidate_assigned_slot_map_ after the loop, to avoid stale
+  // references resulting from calls to slot->DidRecalcAssignedNodes().
+  HeapHashMap<Member<Node>, Member<HTMLSlotElement>> candidate_map;
+
   for (Node& child : NodeTraversal::ChildrenOf(owner_->host())) {
     if (!child.IsSlotable())
       continue;
 
     HTMLSlotElement* slot = nullptr;
     if (!is_user_agent) {
-      if (owner_->IsManualSlotting()) {
+      if (is_manual_slot_assignment) {
         if (auto* candidate_slot = candidate_assigned_slot_map_.at(&child)) {
           if (candidate_slot->ContainingShadowRoot() == owner_) {
             slot = candidate_slot;
@@ -300,6 +305,8 @@
 
     if (slot) {
       slot->AppendAssignedNode(child);
+      if (is_manual_slot_assignment)
+        candidate_map.Set(&child, slot);
     } else {
       child.ClearFlatTreeNodeData();
       child.RemovedFromFlatTree();
@@ -314,6 +321,9 @@
 
   for (auto& slot : Slots())
     slot->DidRecalcAssignedNodes();
+
+  if (is_manual_slot_assignment)
+    candidate_assigned_slot_map_.swap(candidate_map);
 }
 
 const HeapVector<Member<HTMLSlotElement>>& SlotAssignment::Slots() {
diff --git a/third_party/blink/renderer/core/fetch/DEPS b/third_party/blink/renderer/core/fetch/DEPS
index 628e2fdb..82e8abc 100644
--- a/third_party/blink/renderer/core/fetch/DEPS
+++ b/third_party/blink/renderer/core/fetch/DEPS
@@ -4,6 +4,7 @@
     "+mojo/public/cpp/system/data_pipe_utils.h",
     "+mojo/public/cpp/system/simple_watcher.h",
     "+net/base/request_priority.h",
-    "+services/network/public/cpp/content_security_policy/content_security_policy.h",
+    "+services/network/public/cpp",
+    "+services/network/public/mojom",
     "+url/gurl.h",
 ]
diff --git a/third_party/blink/renderer/core/fetch/fetch_response_data.cc b/third_party/blink/renderer/core/fetch/fetch_response_data.cc
index 23077d5..c10237b 100644
--- a/third_party/blink/renderer/core/fetch/fetch_response_data.cc
+++ b/third_party/blink/renderer/core/fetch/fetch_response_data.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/core/fetch/fetch_response_data.h"
 
+#include "services/network/public/cpp/cross_origin_embedder_policy.h"
+#include "services/network/public/cpp/features.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_response.mojom-blink.h"
 #include "third_party/blink/renderer/core/fetch/fetch_header_list.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
@@ -267,6 +269,25 @@
     response->headers.insert(header.first, header.second);
   response->content_security_policy = ParseContentSecurityPolicy(
       HeaderList()->GetAsRawString(status_, status_message_), request_url);
+
+  if (base::FeatureList::IsEnabled(
+          network::features::kCrossOriginEmbedderPolicy)) {
+    network::CrossOriginEmbedderPolicy coep;
+    auto it =
+        response->headers.find(network::CrossOriginEmbedderPolicy::kHeaderName);
+    if (it != response->headers.end()) {
+      std::tie(coep.value, coep.reporting_endpoint) =
+          network::CrossOriginEmbedderPolicy::Parse(it->value.Utf8());
+    }
+    it = response->headers.find(
+        network::CrossOriginEmbedderPolicy::kReportOnlyHeaderName);
+    if (it != response->headers.end()) {
+      std::tie(coep.report_only_value, coep.report_only_reporting_endpoint) =
+          network::CrossOriginEmbedderPolicy::Parse(it->value.Utf8());
+    }
+    response->cross_origin_embedder_policy = std::move(coep);
+  }
+
   return response;
 }
 
diff --git a/third_party/blink/renderer/core/frame/BUILD.gn b/third_party/blink/renderer/core/frame/BUILD.gn
index f6c78c5d..0fd92b9 100644
--- a/third_party/blink/renderer/core/frame/BUILD.gn
+++ b/third_party/blink/renderer/core/frame/BUILD.gn
@@ -92,6 +92,8 @@
     "intervention.h",
     "intervention_report_body.cc",
     "intervention_report_body.h",
+    "is_input_pending_options.cc",
+    "is_input_pending_options.h",
     "layout_subtree_root_list.cc",
     "layout_subtree_root_list.h",
     "local_dom_window.cc",
diff --git a/third_party/blink/renderer/core/frame/is_input_pending_options.cc b/third_party/blink/renderer/core/frame/is_input_pending_options.cc
new file mode 100644
index 0000000..83bdfc16
--- /dev/null
+++ b/third_party/blink/renderer/core/frame/is_input_pending_options.cc
@@ -0,0 +1,19 @@
+// 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 "third_party/blink/renderer/core/frame/is_input_pending_options.h"
+#include "third_party/blink/renderer/core/frame/is_input_pending_options_init.h"
+
+namespace blink {
+
+IsInputPendingOptions* IsInputPendingOptions::Create(
+    const IsInputPendingOptionsInit* options_init) {
+  return MakeGarbageCollected<IsInputPendingOptions>(
+      options_init->includeContinuous());
+}
+
+IsInputPendingOptions::IsInputPendingOptions(bool include_continuous)
+    : include_continuous_(include_continuous) {}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/is_input_pending_options.h b/third_party/blink/renderer/core/frame/is_input_pending_options.h
new file mode 100644
index 0000000..f7036d8
--- /dev/null
+++ b/third_party/blink/renderer/core/frame/is_input_pending_options.h
@@ -0,0 +1,34 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_IS_INPUT_PENDING_OPTIONS_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_IS_INPUT_PENDING_OPTIONS_H_
+
+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+
+namespace blink {
+
+class IsInputPendingOptionsInit;
+
+class IsInputPendingOptions : public ScriptWrappable {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  static IsInputPendingOptions* Create(
+      const IsInputPendingOptionsInit* options_init);
+
+  explicit IsInputPendingOptions(bool include_continuous);
+
+  bool includeContinuous() const { return include_continuous_; }
+  void setIncludeContinuous(bool include_continuous) {
+    include_continuous_ = include_continuous;
+  }
+
+ private:
+  bool include_continuous_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_IS_INPUT_PENDING_OPTIONS_H_
diff --git a/third_party/blink/renderer/core/frame/is_input_pending_options.idl b/third_party/blink/renderer/core/frame/is_input_pending_options.idl
new file mode 100644
index 0000000..19c8fec
--- /dev/null
+++ b/third_party/blink/renderer/core/frame/is_input_pending_options.idl
@@ -0,0 +1,10 @@
+// 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.
+
+// https://github.com/WICG/is-input-pending
+[RuntimeEnabled=ExperimentalIsInputPending]
+interface IsInputPendingOptions {
+    constructor(optional IsInputPendingOptionsInit options = {});
+    attribute boolean includeContinuous;
+};
diff --git a/third_party/blink/renderer/core/frame/is_input_pending_options_init.idl b/third_party/blink/renderer/core/frame/is_input_pending_options_init.idl
new file mode 100644
index 0000000..a818a17
--- /dev/null
+++ b/third_party/blink/renderer/core/frame/is_input_pending_options_init.idl
@@ -0,0 +1,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.
+
+// https://github.com/WICG/is-input-pending
+dictionary IsInputPendingOptionsInit {
+  boolean includeContinuous = false;
+};
diff --git a/third_party/blink/renderer/core/frame/navigator_scheduling.idl b/third_party/blink/renderer/core/frame/navigator_scheduling.idl
index 76d9e9b..a07d85f 100644
--- a/third_party/blink/renderer/core/frame/navigator_scheduling.idl
+++ b/third_party/blink/renderer/core/frame/navigator_scheduling.idl
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://github.com/tdresser/is-input-pending
+// https://github.com/WICG/is-input-pending
 [
     Exposed=Window,
     ImplementedAs=NavigatorScheduling,
diff --git a/third_party/blink/renderer/core/frame/scheduling.cc b/third_party/blink/renderer/core/frame/scheduling.cc
index cd659ee2..d270b559 100644
--- a/third_party/blink/renderer/core/frame/scheduling.cc
+++ b/third_party/blink/renderer/core/frame/scheduling.cc
@@ -5,61 +5,30 @@
 #include "third_party/blink/renderer/core/frame/scheduling.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
-#include "third_party/blink/renderer/core/inspector/console_message.h"
-#include "third_party/blink/renderer/platform/heap/heap.h"
+#include "third_party/blink/renderer/core/frame/is_input_pending_options.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input.h"
-#include "third_party/blink/renderer/platform/scheduler/public/pending_user_input_type.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
-#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
-#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
 namespace blink {
 
 bool Scheduling::isInputPending(ScriptState* script_state,
-                                const Vector<String>& input_types) const {
+                                const IsInputPendingOptions* options) const {
   DCHECK(RuntimeEnabledFeatures::ExperimentalIsInputPendingEnabled(
       ExecutionContext::From(script_state)));
 
-  if (!Platform::Current()->IsLockedToSite()) {
-    // As we're interested in checking pending events for as many frames as we
-    // can on the main thread, restrict the API to the case where all frames in
-    // a process are part of the same site to avoid leaking cross-site inputs.
-    ExecutionContext::From(script_state)
-        ->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
-            mojom::ConsoleMessageSource::kJavaScript,
-            mojom::ConsoleMessageLevel::kWarning,
-            "isInputPending requires site-per-process (crbug.com/910421)."));
+  auto* frame = LocalDOMWindow::From(script_state)->GetFrame();
+  if (!frame)
     return false;
-  }
 
   auto* scheduler = ThreadScheduler::Current();
-  auto input_info = scheduler->GetPendingUserInputInfo();
-  if (input_types.size() == 0) {
-    // If unspecified, return true if any input type is pending.
-    return input_info.HasPendingInputType(
-        scheduler::PendingUserInputType::kAny);
-  }
+  auto info = scheduler->GetPendingUserInputInfo(options->includeContinuous());
 
-  bool has_pending_input = false;
-  for (const String& input_type_string : input_types) {
-    const auto pending_input_type = scheduler::PendingUserInput::TypeFromString(
-        AtomicString(input_type_string));
-    if (pending_input_type == scheduler::PendingUserInputType::kNone) {
-      StringBuilder message;
-      message.Append("Unknown input event type \"");
-      message.Append(input_type_string);
-      message.Append("\". Skipping.");
-      ExecutionContext::From(script_state)
-          ->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
-              mojom::ConsoleMessageSource::kJavaScript,
-              mojom::ConsoleMessageLevel::kWarning, message.ToString()));
-    }
-
-    if (!has_pending_input)
-      has_pending_input |= input_info.HasPendingInputType(pending_input_type);
-  }
-  return has_pending_input;
+  // TODO(acomminos): Attribution first requires a reverse mapping between
+  // cc::ElementId instances and their underlying Document* objects.
+  (void)info;
+  return false;
 }
 
 bool Scheduling::isFramePending() const {
diff --git a/third_party/blink/renderer/core/frame/scheduling.h b/third_party/blink/renderer/core/frame/scheduling.h
index 55df6f8..b2f35f1 100644
--- a/third_party/blink/renderer/core/frame/scheduling.h
+++ b/third_party/blink/renderer/core/frame/scheduling.h
@@ -12,12 +12,14 @@
 
 namespace blink {
 
+class IsInputPendingOptions;
+
 // Low-level scheduling primitives for JS scheduler implementations.
 class Scheduling : public ScriptWrappable {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  bool isInputPending(ScriptState*, const Vector<String>& input_types) const;
+  bool isInputPending(ScriptState*, const IsInputPendingOptions* options) const;
   bool isFramePending() const;
 };
 
diff --git a/third_party/blink/renderer/core/frame/scheduling.idl b/third_party/blink/renderer/core/frame/scheduling.idl
index c1ae6538..5e1582d0 100644
--- a/third_party/blink/renderer/core/frame/scheduling.idl
+++ b/third_party/blink/renderer/core/frame/scheduling.idl
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://github.com/tdresser/is-input-pending
+// https://github.com/WICG/is-input-pending
 [RuntimeEnabled=ExperimentalIsInputPending]
 interface Scheduling {
     [RuntimeEnabled=ExperimentalIsInputPending] boolean isFramePending();
-    [CallWith=ScriptState, MeasureAs=SchedulingIsInputPending, RuntimeEnabled=ExperimentalIsInputPending] boolean isInputPending(optional sequence<DOMString> inputTypes = []);
+    [CallWith=ScriptState, MeasureAs=SchedulingIsInputPending, RuntimeEnabled=ExperimentalIsInputPending] boolean isInputPending(IsInputPendingOptions options);
 };
diff --git a/third_party/blink/renderer/core/html/forms/base_button_input_type.cc b/third_party/blink/renderer/core/html/forms/base_button_input_type.cc
index 6fc142b..793b040 100644
--- a/third_party/blink/renderer/core/html/forms/base_button_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/base_button_input_type.cc
@@ -33,6 +33,7 @@
 
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/dom/text.h"
+#include "third_party/blink/renderer/core/frame/web_feature.h"
 #include "third_party/blink/renderer/core/html/forms/html_form_element.h"
 #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
 #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
@@ -80,6 +81,8 @@
 
 LayoutObject* BaseButtonInputType::CreateLayoutObject(const ComputedStyle&,
                                                       LegacyLayout) const {
+  UseCounter::Count(GetElement().GetDocument(),
+                    WebFeature::kLegacyLayoutByButton);
   return new LayoutButton(&GetElement());
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/file_input_type.cc b/third_party/blink/renderer/core/html/forms/file_input_type.cc
index 62d3be9..c6b772fc 100644
--- a/third_party/blink/renderer/core/html/forms/file_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/file_input_type.cc
@@ -208,7 +208,11 @@
 }
 
 bool FileInputType::TypeShouldForceLegacyLayout() const {
-  return !RuntimeEnabledFeatures::LayoutNGForControlsEnabled();
+  if (RuntimeEnabledFeatures::LayoutNGForControlsEnabled())
+    return false;
+  UseCounter::Count(GetElement().GetDocument(),
+                    WebFeature::kLegacyLayoutByFileUploadControl);
+  return true;
 }
 
 LayoutObject* FileInputType::CreateLayoutObject(const ComputedStyle& style,
diff --git a/third_party/blink/renderer/core/html/forms/html_button_element.cc b/third_party/blink/renderer/core/html/forms/html_button_element.cc
index e062da10..1ff4dbb 100644
--- a/third_party/blink/renderer/core/html/forms/html_button_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_button_element.cc
@@ -27,6 +27,7 @@
 
 #include "third_party/blink/renderer/core/dom/attribute.h"
 #include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/core/frame/web_feature.h"
 #include "third_party/blink/renderer/core/html/forms/form_data.h"
 #include "third_party/blink/renderer/core/html/forms/html_form_element.h"
 #include "third_party/blink/renderer/core/html_names.h"
@@ -53,6 +54,7 @@
       display == EDisplay::kInlineLayoutCustom ||
       display == EDisplay::kLayoutCustom)
     return HTMLFormControlElement::CreateLayoutObject(style, legacy);
+  UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByButton);
   return new LayoutButton(this);
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.cc b/third_party/blink/renderer/core/html/forms/html_select_element.cc
index 555520d..f8dcd10 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.cc
@@ -368,7 +368,11 @@
 }
 
 bool HTMLSelectElement::TypeShouldForceLegacyLayout() const {
-  return UsesMenuList();
+  if (UsesMenuList()) {
+    UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByMenuList);
+    return true;
+  }
+  return false;
 }
 
 LayoutObject* HTMLSelectElement::CreateLayoutObject(
diff --git a/third_party/blink/renderer/core/html/forms/html_text_area_element.cc b/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
index d10fd35..04bcc8532 100644
--- a/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
@@ -214,6 +214,7 @@
 
 LayoutObject* HTMLTextAreaElement::CreateLayoutObject(const ComputedStyle&,
                                                       LegacyLayout) {
+  UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByTextControl);
   return new LayoutTextControlMultiLine(this);
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc b/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc
index 50036dd..e9e58542 100644
--- a/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc
+++ b/third_party/blink/renderer/core/html/forms/picker_indicator_element.cc
@@ -63,6 +63,7 @@
   if (features::IsFormControlsRefreshEnabled())
     return HTMLDivElement::CreateLayoutObject(style, legacy);
 
+  UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByDetailsMarker);
   return new LayoutDetailsMarker(this);
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/range_input_type.cc b/third_party/blink/renderer/core/html/forms/range_input_type.cc
index 0eed482..253ea5a2 100644
--- a/third_party/blink/renderer/core/html/forms/range_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/range_input_type.cc
@@ -259,6 +259,8 @@
 
 LayoutObject* RangeInputType::CreateLayoutObject(const ComputedStyle&,
                                                  LegacyLayout) const {
+  UseCounter::Count(GetElement().GetDocument(),
+                    WebFeature::kLegacyLayoutBySlider);
   return new LayoutSlider(&GetElement());
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/text_field_input_type.cc b/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
index efb2e1e..07164dd 100644
--- a/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
@@ -66,6 +66,7 @@
 
   LayoutObject* CreateLayoutObject(const ComputedStyle&,
                                    LegacyLayout) override {
+    UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByDetailsMarker);
     return new LayoutDetailsMarker(this);
   }
 
@@ -288,6 +289,8 @@
 
 LayoutObject* TextFieldInputType::CreateLayoutObject(const ComputedStyle&,
                                                      LegacyLayout) const {
+  UseCounter::Count(GetElement().GetDocument(),
+                    WebFeature::kLegacyLayoutByTextControl);
   return new LayoutTextControlSingleLine(&GetElement());
 }
 
diff --git a/third_party/blink/renderer/core/html/html_rt_element.cc b/third_party/blink/renderer/core/html/html_rt_element.cc
index fba2fe9..8a018a0a 100644
--- a/third_party/blink/renderer/core/html/html_rt_element.cc
+++ b/third_party/blink/renderer/core/html/html_rt_element.cc
@@ -14,8 +14,10 @@
 
 LayoutObject* HTMLRTElement::CreateLayoutObject(const ComputedStyle& style,
                                                 LegacyLayout legacy) {
-  if (style.Display() == EDisplay::kBlock)
+  if (style.Display() == EDisplay::kBlock) {
+    UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByRuby);
     return new LayoutRubyText(this);
+  }
   return LayoutObject::CreateObject(this, style, legacy);
 }
 
diff --git a/third_party/blink/renderer/core/html/html_ruby_element.cc b/third_party/blink/renderer/core/html/html_ruby_element.cc
index c40fddf9..e2f1d7c 100644
--- a/third_party/blink/renderer/core/html/html_ruby_element.cc
+++ b/third_party/blink/renderer/core/html/html_ruby_element.cc
@@ -14,10 +14,14 @@
 
 LayoutObject* HTMLRubyElement::CreateLayoutObject(const ComputedStyle& style,
                                                   LegacyLayout legacy) {
-  if (style.Display() == EDisplay::kInline)
+  if (style.Display() == EDisplay::kInline) {
+    UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByRuby);
     return new LayoutRubyAsInline(this);
-  if (style.Display() == EDisplay::kBlock)
+  }
+  if (style.Display() == EDisplay::kBlock) {
+    UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByRuby);
     return new LayoutRubyAsBlock(this);
+  }
   return LayoutObject::CreateObject(this, style, legacy);
 }
 
diff --git a/third_party/blink/renderer/core/html/html_slot_element.cc b/third_party/blink/renderer/core/html/html_slot_element.cc
index e35740e..0a6ec107 100644
--- a/third_party/blink/renderer/core/html/html_slot_element.cc
+++ b/third_party/blink/renderer/core/html/html_slot_element.cc
@@ -171,6 +171,21 @@
   return elements;
 }
 
+bool HTMLSlotElement::CheckNodesValidity(HeapVector<Member<Node>> nodes,
+                                         ExceptionState& exception_state) {
+  auto* host = OwnerShadowHost();
+  for (auto& node : nodes) {
+    if (node->parentNode() != host) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kNotAllowedError,
+          "Node:  '" + node->nodeName() +
+              "' is invalid for manual slot assignment.");
+      return false;
+    }
+  }
+  return true;
+}
+
 void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes,
                              ExceptionState& exception_state) {
   if (!SupportsAssignment() || !ContainingShadowRoot()->IsManualSlotting()) {
@@ -180,30 +195,19 @@
     return;
   }
 
+  if (!CheckNodesValidity(nodes, exception_state))
+    return;
+
   ContainingShadowRoot()->GetSlotAssignment().ClearCandidateNodes(
       assigned_nodes_candidates_);
   assigned_nodes_candidates_.clear();
-  auto* host = OwnerShadowHost();
-  bool has_invalid_node = false;
   for (auto& node : nodes) {
-    if (node->parentNode() != host) {
-      exception_state.ThrowDOMException(
-          DOMExceptionCode::kNotAllowedError,
-          "Node:  '" + node->nodeName() +
-              "' is invalid for manual slot assignment.");
-      assigned_nodes_candidates_.clear();
-      has_invalid_node = true;
-      break;
-    }
-
     // Before assignment, see if this node belongs to another slot.
     ContainingShadowRoot()->GetSlotAssignment().UpdateCandidateNodeAssignedSlot(
         *node, *this);
     assigned_nodes_candidates_.AppendOrMoveToLast(node);
   }
-
-  if (!has_invalid_node)
-    ContainingShadowRoot()->GetSlotAssignment().SetNeedsAssignmentRecalc();
+  ContainingShadowRoot()->GetSlotAssignment().SetNeedsAssignmentRecalc();
 }
 
 void HTMLSlotElement::AppendAssignedNode(Node& host_child) {
diff --git a/third_party/blink/renderer/core/html/html_slot_element.h b/third_party/blink/renderer/core/html/html_slot_element.h
index 5468f2c..b488910 100644
--- a/third_party/blink/renderer/core/html/html_slot_element.h
+++ b/third_party/blink/renderer/core/html/html_slot_element.h
@@ -138,6 +138,7 @@
       const HeapVector<Member<Node>>& new_slotted);
 
   void SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc();
+  bool CheckNodesValidity(HeapVector<Member<Node>> nodes, ExceptionState&);
 
   // SlotAssignnment:recalc runs in tree order. Update to assigned order.
   void UpdateManuallyAssignedNodesOrdering();
diff --git a/third_party/blink/renderer/core/html/shadow/details_marker_control.cc b/third_party/blink/renderer/core/html/shadow/details_marker_control.cc
index d61e459c..2e54345 100644
--- a/third_party/blink/renderer/core/html/shadow/details_marker_control.cc
+++ b/third_party/blink/renderer/core/html/shadow/details_marker_control.cc
@@ -42,6 +42,7 @@
 
 LayoutObject* DetailsMarkerControl::CreateLayoutObject(const ComputedStyle&,
                                                        LegacyLayout) {
+  UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByDetailsMarker);
   return new LayoutDetailsMarker(this);
 }
 
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
index 80b58be6..ab8cbab 100644
--- a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
@@ -237,6 +237,7 @@
   if (style.GetPosition() == EPosition::kRelative)
     return HTMLDivElement::CreateLayoutObject(style, legacy);
 
+  UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByVTTCue);
   return new LayoutVTTCue(this, snap_to_lines_position_);
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index c51b6bdc..38c270ded 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -247,15 +247,23 @@
       return LayoutObjectFactory::CreateBlockFlow(*element, style, legacy);
     case EDisplay::kTable:
     case EDisplay::kInlineTable:
+      UseCounter::Count(element->GetDocument(),
+                        WebFeature::kLegacyLayoutByTable);
       return new LayoutTable(element);
     case EDisplay::kTableRowGroup:
     case EDisplay::kTableHeaderGroup:
     case EDisplay::kTableFooterGroup:
+      UseCounter::Count(element->GetDocument(),
+                        WebFeature::kLegacyLayoutByTable);
       return new LayoutTableSection(element);
     case EDisplay::kTableRow:
+      UseCounter::Count(element->GetDocument(),
+                        WebFeature::kLegacyLayoutByTable);
       return new LayoutTableRow(element);
     case EDisplay::kTableColumnGroup:
     case EDisplay::kTableColumn:
+      UseCounter::Count(element->GetDocument(),
+                        WebFeature::kLegacyLayoutByTable);
       return new LayoutTableCol(element);
     case EDisplay::kTableCell:
       return LayoutObjectFactory::CreateTableCell(*element, style, legacy);
@@ -268,8 +276,13 @@
         return LayoutObjectFactory::CreateBlockForLineClamp(*element, style,
                                                             legacy);
       }
-      if (style.IsDeprecatedFlexboxUsingFlexLayout())
+      if (style.IsDeprecatedFlexboxUsingFlexLayout()) {
+        UseCounter::Count(element->GetDocument(),
+                          WebFeature::kLegacyLayoutByFlexBox);
         return new LayoutFlexibleBox(element);
+      }
+      UseCounter::Count(element->GetDocument(),
+                        WebFeature::kLegacyLayoutByDeprecatedFlexBox);
       return new LayoutDeprecatedFlexibleBox(element);
     case EDisplay::kFlex:
     case EDisplay::kInlineFlex:
diff --git a/third_party/blink/renderer/core/layout/layout_object_factory.cc b/third_party/blink/renderer/core/layout/layout_object_factory.cc
index 7d46b322..49df1d2 100644
--- a/third_party/blink/renderer/core/layout/layout_object_factory.cc
+++ b/third_party/blink/renderer/core/layout/layout_object_factory.cc
@@ -105,6 +105,8 @@
                                                     const ComputedStyle& style,
                                                     LegacyLayout legacy) {
   bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGFlexBoxEnabled();
+  if (disable_ng_for_type)
+    UseCounter::Count(node.GetDocument(), WebFeature::kLegacyLayoutByFlexBox);
   return CreateObject<LayoutBlock, LayoutNGFlexibleBox, LayoutFlexibleBox>(
       node, style, legacy, disable_ng_for_type);
 }
@@ -113,6 +115,8 @@
                                              const ComputedStyle& style,
                                              LegacyLayout legacy) {
   bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGGridEnabled();
+  if (disable_ng_for_type)
+    UseCounter::Count(node.GetDocument(), WebFeature::kLegacyLayoutByGrid);
   return CreateObject<LayoutBlock, LayoutNGGrid, LayoutGrid>(
       node, style, legacy, disable_ng_for_type);
 }
@@ -152,6 +156,8 @@
                                                  const ComputedStyle& style,
                                                  LegacyLayout legacy) {
   bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGFieldsetEnabled();
+  if (disable_ng_for_type)
+    UseCounter::Count(node.GetDocument(), WebFeature::kLegacyLayoutByFieldSet);
   return CreateObject<LayoutBlock, LayoutNGFieldset, LayoutFieldset>(
       node, style, legacy, disable_ng_for_type);
 }
diff --git a/third_party/blink/renderer/core/layout/text_decoration_offset_base.cc b/third_party/blink/renderer/core/layout/text_decoration_offset_base.cc
index 059775d..ae99892 100644
--- a/third_party/blink/renderer/core/layout/text_decoration_offset_base.cc
+++ b/third_party/blink/renderer/core/layout/text_decoration_offset_base.cc
@@ -5,6 +5,9 @@
 #include "third_party/blink/renderer/core/layout/text_decoration_offset_base.h"
 
 #include <algorithm>
+
+#include <base/optional.h>
+
 #include "third_party/blink/renderer/core/paint/decoration_info.h"
 #include "third_party/blink/renderer/platform/fonts/font_metrics.h"
 #include "third_party/blink/renderer/platform/fonts/font_vertical_position_type.h"
@@ -27,6 +30,14 @@
   return font_metrics.Ascent() + gap;
 }
 
+base::Optional<int> ComputeUnderlineOffsetFromFont(
+    const blink::FontMetrics& font_metrics) {
+  if (!font_metrics.UnderlinePosition())
+    return base::nullopt;
+
+  return roundf(font_metrics.FloatAscent() + *font_metrics.UnderlinePosition());
+}
+
 }  // namespace
 
 namespace blink {
@@ -41,8 +52,9 @@
       FALLTHROUGH;
     case ResolvedUnderlinePosition::kNearAlphabeticBaselineFromFont:
       DCHECK(RuntimeEnabledFeatures::UnderlineOffsetThicknessEnabled());
-      // TODO(https://crbug.com/785230): Implement in subsequent CL.
-      FALLTHROUGH;
+      return ComputeUnderlineOffsetFromFont(font_metrics)
+          .value_or(ComputeUnderlineOffsetAuto(font_metrics,
+                                               text_decoration_thickness));
     case ResolvedUnderlinePosition::kNearAlphabeticBaselineAuto:
       return ComputeUnderlineOffsetAuto(font_metrics,
                                         text_decoration_thickness);
diff --git a/third_party/blink/renderer/core/paint/text_painter_base.cc b/third_party/blink/renderer/core/paint/text_painter_base.cc
index 7648d4a..813881f 100644
--- a/third_party/blink/renderer/core/paint/text_painter_base.cc
+++ b/third_party/blink/renderer/core/paint/text_painter_base.cc
@@ -326,24 +326,17 @@
 
 float ComputeDecorationThickness(const ComputedStyle* style,
                                  const SimpleFontData* font_data) {
-  // Set the thick of the line to be 10% (or something else ?)of the computed
-  // font size and not less than 1px.  Using computedFontSize should take care
-  // of zoom as well.
+  // TODO(https://crbug.com/785230): Implement text-decoration-thickness setting
+  // and the from-font keyword here. We previously tried reading
+  // font_data->FontMetrics().UnderlineThickness() here but that never returned
+  // anything other than 0. Removed no-op implementation until we implement
+  // from-font behavior here. Keep font_data argument for now as this will be
+  // needed for the from-font implementation.
 
-  // Update Underline thickness, in case we have Faulty Font Metrics calculating
-  // underline thickness by old method.
-  float text_decoration_thickness = 0.0;
-  int font_height_int = 0;
-  if (font_data) {
-    text_decoration_thickness =
-        font_data->GetFontMetrics().UnderlineThickness();
-    font_height_int = font_data->GetFontMetrics().Height();
-  }
-  if ((text_decoration_thickness == 0.f) ||
-      (text_decoration_thickness >= (font_height_int >> 1))) {
-    text_decoration_thickness = std::max(1.f, style->ComputedFontSize() / 10.f);
-  }
-  return text_decoration_thickness;
+  // Set the thickness of the line to be 10% (or something else ?)of the
+  // computed font size and not less than 1px.  Using computedFontSize should
+  // take care of zoom as well.
+  return std::max(1.f, style->ComputedFontSize() / 10.f);
 }
 
 }  // anonymous namespace
diff --git a/third_party/blink/renderer/core/svg/svg_svg_element.cc b/third_party/blink/renderer/core/svg/svg_svg_element.cc
index 78b88fe..116c260 100644
--- a/third_party/blink/renderer/core/svg/svg_svg_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_svg_element.cc
@@ -506,6 +506,7 @@
 
 LayoutObject* SVGSVGElement::CreateLayoutObject(const ComputedStyle&,
                                                 LegacyLayout) {
+  UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutBySVG);
   if (IsOutermostSVGSVGElement())
     return new LayoutSVGRoot(this);
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index d53b5d4..abac3fe 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -3413,8 +3413,8 @@
   // Node might not have a LayoutObject due to the fact that it is in a locked
   // subtree. Force the update to create the LayoutObject (and update position
   // information) for this node.
-  DisplayLockUtilities::ScopedChainForcedUpdate scoped_force_update(node);
-  GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kDisplayLock);
+  GetDocument()->UpdateStyleAndLayoutForNode(
+      node, DocumentUpdateReason::kDisplayLock);
 
   LayoutObject* layout_object = node->GetLayoutObject();
   if (!layout_object)
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device.cc b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
index eb67079..a727c9e 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_device.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
@@ -46,10 +46,8 @@
           this,
           GetProcs().deviceCreateQueue(GetHandle()))),
       lost_property_(MakeGarbageCollected<LostProperty>(execution_context)),
-      error_callback_(
-          BindRepeatingDawnCallback(&GPUDevice::OnUncapturedError,
-                                    WrapWeakPersistent(this),
-                                    WrapWeakPersistent(execution_context))),
+      error_callback_(BindRepeatingDawnCallback(&GPUDevice::OnUncapturedError,
+                                                WrapWeakPersistent(this))),
       client_id_(client_id) {
   DCHECK(dawn_control_client->GetInterface()->GetDevice(client_id));
   GetProcs().deviceSetUncapturedErrorCallback(
@@ -70,17 +68,31 @@
   return client_id_;
 }
 
-void GPUDevice::OnUncapturedError(ExecutionContext* execution_context,
-                                  WGPUErrorType errorType,
-                                  const char* message) {
-  if (execution_context) {
-    DCHECK_NE(errorType, WGPUErrorType_NoError);
-    LOG(ERROR) << "GPUDevice: " << message;
+void GPUDevice::AddConsoleWarning(const char* message) {
+  ExecutionContext* execution_context = GetExecutionContext();
+  if (execution_context && allowed_console_warnings_remaining_ > 0) {
     auto* console_message = MakeGarbageCollected<ConsoleMessage>(
-        mojom::ConsoleMessageSource::kRendering,
-        mojom::ConsoleMessageLevel::kWarning, message);
+        mojom::blink::ConsoleMessageSource::kRendering,
+        mojom::blink::ConsoleMessageLevel::kWarning, message);
     execution_context->AddConsoleMessage(console_message);
+
+    allowed_console_warnings_remaining_--;
+    if (allowed_console_warnings_remaining_ == 0) {
+      auto* final_message = MakeGarbageCollected<ConsoleMessage>(
+          mojom::blink::ConsoleMessageSource::kRendering,
+          mojom::blink::ConsoleMessageLevel::kWarning,
+          "WebGPU: too many warnings, no more warnings will be reported to the "
+          "console for this GPUDevice.");
+      execution_context->AddConsoleMessage(final_message);
+    }
   }
+}
+
+void GPUDevice::OnUncapturedError(WGPUErrorType errorType,
+                                  const char* message) {
+  DCHECK_NE(errorType, WGPUErrorType_NoError);
+  LOG(ERROR) << "GPUDevice: " << message;
+  AddConsoleWarning(message);
 
   // TODO: Use device lost callback instead of uncaptured error callback.
   if (errorType == WGPUErrorType_DeviceLost &&
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device.h b/third_party/blink/renderer/modules/webgpu/gpu_device.h
index c04fd6f..4ce25d8 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_device.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_device.h
@@ -108,13 +108,13 @@
   const AtomicString& InterfaceName() const override;
   ExecutionContext* GetExecutionContext() const override;
 
+  void AddConsoleWarning(const char* message);
+
  private:
   using LostProperty =
       ScriptPromiseProperty<Member<GPUDeviceLostInfo>, ToV8UndefinedGenerator>;
 
-  void OnUncapturedError(ExecutionContext* execution_context,
-                         WGPUErrorType errorType,
-                         const char* message);
+  void OnUncapturedError(WGPUErrorType errorType, const char* message);
 
   void OnPopErrorScopeCallback(ScriptPromiseResolver* resolver,
                                WGPUErrorType type,
@@ -129,6 +129,9 @@
 
   uint64_t client_id_;
 
+  static constexpr int kMaxAllowedConsoleWarnings = 500;
+  int allowed_console_warnings_remaining_ = kMaxAllowedConsoleWarnings;
+
   DISALLOW_COPY_AND_ASSIGN(GPUDevice);
 };
 
diff --git a/third_party/blink/renderer/modules/xr/xr_session_init.idl b/third_party/blink/renderer/modules/xr/xr_session_init.idl
index 705227f..11d315b 100644
--- a/third_party/blink/renderer/modules/xr/xr_session_init.idl
+++ b/third_party/blink/renderer/modules/xr/xr_session_init.idl
@@ -6,5 +6,5 @@
 dictionary XRSessionInit {
   sequence<any> requiredFeatures;
   sequence<any> optionalFeatures;
-  [RuntimeEnabled=WebXRIncubations] XRDOMOverlayInit domOverlay;
+  XRDOMOverlayInit domOverlay;
 };
diff --git a/third_party/blink/renderer/platform/fonts/font_metrics.h b/third_party/blink/renderer/platform/fonts/font_metrics.h
index 5cfbc45..ef1a4320 100644
--- a/third_party/blink/renderer/platform/fonts/font_metrics.h
+++ b/third_party/blink/renderer/platform/fonts/font_metrics.h
@@ -20,6 +20,8 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_FONT_METRICS_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_FONT_METRICS_H_
 
+#include <base/optional.h>
+
 #include "third_party/blink/renderer/platform/fonts/font_baseline.h"
 #include "third_party/blink/renderer/platform/geometry/layout_unit.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
@@ -44,8 +46,6 @@
         line_spacing_(0),
         x_height_(0),
         zero_width_(0),
-        underlinethickness_(0),
-        underline_position_(0),
         ascent_int_(0),
         descent_int_(0),
         has_x_height_(false),
@@ -148,12 +148,16 @@
     has_zero_width_ = has_zero_width;
   }
 
-  float UnderlineThickness() const { return underlinethickness_; }
+  base::Optional<float> UnderlineThickness() const {
+    return underline_thickness_;
+  }
   void SetUnderlineThickness(float underline_thickness) {
-    underlinethickness_ = underline_thickness;
+    underline_thickness_ = underline_thickness;
   }
 
-  float UnderlinePosition() const { return underline_position_; }
+  base::Optional<float> UnderlinePosition() const {
+    return underline_position_;
+  }
   void SetUnderlinePosition(float underline_position) {
     underline_position_ = underline_position;
   }
@@ -184,8 +188,8 @@
     line_spacing_ = 0;
     x_height_ = 0;
     has_x_height_ = false;
-    underlinethickness_ = 0;
-    underline_position_ = 0;
+    underline_thickness_.reset();
+    underline_position_.reset();
   }
 
   unsigned units_per_em_;
@@ -195,8 +199,8 @@
   float line_spacing_;
   float x_height_;
   float zero_width_;
-  float underlinethickness_;
-  float underline_position_;
+  base::Optional<float> underline_thickness_ = base::nullopt;
+  base::Optional<float> underline_position_ = base::nullopt;
   int ascent_int_;
   int descent_int_;
   bool has_x_height_;
diff --git a/third_party/blink/renderer/platform/fonts/simple_font_data.cc b/third_party/blink/renderer/platform/fonts/simple_font_data.cc
index 54e01dd9c..0a959be 100644
--- a/third_party/blink/renderer/platform/fonts/simple_font_data.cc
+++ b/third_party/blink/renderer/platform/fonts/simple_font_data.cc
@@ -94,6 +94,12 @@
   font_metrics_.SetAscent(ascent);
   font_metrics_.SetDescent(descent);
 
+  float skia_underline_value;
+  if (metrics.hasUnderlinePosition(&skia_underline_value))
+    font_metrics_.SetUnderlinePosition(skia_underline_value);
+  if (metrics.hasUnderlineThickness(&skia_underline_value))
+    font_metrics_.SetUnderlineThickness(skia_underline_value);
+
   float x_height;
   if (metrics.fXHeight) {
     x_height = metrics.fXHeight;
diff --git a/third_party/blink/renderer/platform/network/http_parsers_fuzzer.cc b/third_party/blink/renderer/platform/network/http_parsers_fuzzer.cc
index 88acd32..ebb0130 100644
--- a/third_party/blink/renderer/platform/network/http_parsers_fuzzer.cc
+++ b/third_party/blink/renderer/platform/network/http_parsers_fuzzer.cc
@@ -7,8 +7,10 @@
 #include <string>
 
 #include "base/logging.h"
+#include "services/network/public/mojom/content_security_policy.mojom-blink.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
 #include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
@@ -40,5 +42,7 @@
                                        size, &response, &end);
   blink::ParseServerTimingHeader(terminated.c_str());
   blink::ParseContentTypeOptionsHeader(terminated.c_str());
+  blink::ParseContentSecurityPolicy(terminated.c_str(),
+                                    blink::KURL("http://example.com"));
   return 0;
 }
diff --git a/third_party/blink/renderer/platform/network/http_parsers_test.cc b/third_party/blink/renderer/platform/network/http_parsers_test.cc
index 1cf4dde..31b3b32 100644
--- a/third_party/blink/renderer/platform/network/http_parsers_test.cc
+++ b/third_party/blink/renderer/platform/network/http_parsers_test.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/platform/network/http_parsers.h"
 
 #include "base/stl_util.h"
+#include "services/network/public/mojom/content_security_policy.mojom-blink.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
@@ -615,4 +616,201 @@
   }
 }
 
+// -----------------------------------------------------------------------------
+// Blink's HTTP parser is reusing:
+// services/network/public/cpp/content_security_policy/, which is already tested
+// and fuzzed.
+// What needs to be tested is the basic conversion from/to blink types.
+// -----------------------------------------------------------------------------
+
+TEST(HTTPParsersTest, ParseContentSecurityPolicyEmpty) {
+  auto csp = ParseContentSecurityPolicy("HTTP/1.1 200 OK\r\n",
+                                        KURL("http://example.com"));
+  EXPECT_TRUE(csp.IsEmpty());
+}
+
+TEST(HTTPParsersTest, ParseContentSecurityPolicyMultiple) {
+  auto csp = ParseContentSecurityPolicy(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Security-Policy: frame-ancestors a.com\r\n"
+      "Content-Security-Policy: frame-ancestors b.com\r\n",
+      KURL("http://example.com"));
+  ASSERT_EQ(2u, csp.size());
+  EXPECT_EQ("frame-ancestors a.com", csp[0]->header->header_value);
+  EXPECT_EQ("frame-ancestors b.com", csp[1]->header->header_value);
+}
+
+TEST(HTTPParsersTest, ParseContentSecurityPolicyCoalesce) {
+  auto csp = ParseContentSecurityPolicy(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Security-Policy:"
+      "frame-ancestors a.com, frame-ancestors b.com\r\n",
+      KURL("http://example.com"));
+  ASSERT_EQ(2u, csp.size());
+  EXPECT_EQ("frame-ancestors a.com", csp[0]->header->header_value);
+  EXPECT_EQ("frame-ancestors b.com", csp[1]->header->header_value);
+}
+
+TEST(HTTPParsersTest, ParseContentSecurityPolicyHeader) {
+  auto csp = ParseContentSecurityPolicy(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Security-Policy: frame-ancestors a.com\r\n"
+      "Content-Security-Policy-Report-Only: frame-ancestors b.com",
+      KURL("http://example.com"));
+  ASSERT_EQ(2u, csp.size());
+
+  // Header source:
+  EXPECT_EQ(network::mojom::ContentSecurityPolicySource::kHTTP,
+            csp[0]->header->source);
+  EXPECT_EQ(network::mojom::ContentSecurityPolicySource::kHTTP,
+            csp[1]->header->source);
+
+  // Header type:
+  EXPECT_EQ(network::mojom::ContentSecurityPolicyType::kEnforce,
+            csp[0]->header->type);
+  EXPECT_EQ(network::mojom::ContentSecurityPolicyType::kReport,
+            csp[1]->header->type);
+
+  // Header value
+  EXPECT_EQ("frame-ancestors a.com", csp[0]->header->header_value);
+  EXPECT_EQ("frame-ancestors b.com", csp[1]->header->header_value);
+}
+
+TEST(HTTPParsersTest, ParseContentSecurityPolicyDirectiveName) {
+  auto policies = ParseContentSecurityPolicy(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Security-Policy: frame-ancestors 'none'\r\n"
+      "Content-Security-Policy: sandbox allow-script\r\n"
+      "Content-Security-Policy: form-action 'none'\r\n"
+      "Content-Security-Policy: navigate-to'none'\r\n"
+      "Content-Security-Policy: frame-src 'none'\r\n"
+      "Content-Security-Policy: child-src 'none'\r\n"
+      "Content-Security-Policy: script-src 'none'\r\n"
+      "Content-Security-Policy: default-src 'none'\r\n",
+      KURL("http://example.com"));
+  EXPECT_EQ(8u, policies.size());
+  // frame-ancestors
+  EXPECT_EQ(1u, policies[0]->directives.size());
+  // sandbox. TODO(https://crbug.com/1041376) Implement this.
+  EXPECT_EQ(0u, policies[1]->directives.size());
+  // form-action. Not parsed.
+  EXPECT_EQ(0u, policies[2]->directives.size());
+  // navigate-to. Not parsed.
+  EXPECT_EQ(0u, policies[3]->directives.size());
+  // frame-src. Not parsed.
+  EXPECT_EQ(0u, policies[4]->directives.size());
+  // child-src. Not parsed.
+  EXPECT_EQ(0u, policies[5]->directives.size());
+  // script-src. Not parsed.
+  EXPECT_EQ(0u, policies[6]->directives.size());
+  // default-src. Not parsed.
+  EXPECT_EQ(0u, policies[7]->directives.size());
+}
+
+TEST(HTTPParsersTest, ParseContentSecurityPolicyReportTo) {
+  auto policies = ParseContentSecurityPolicy(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Security-Policy: report-to a b\r\n",
+      KURL("http://example.com"));
+  EXPECT_TRUE(policies[0]->use_reporting_api);
+  ASSERT_EQ(2u, policies[0]->report_endpoints.size());
+  EXPECT_EQ("a", policies[0]->report_endpoints[0]);
+  EXPECT_EQ("b", policies[0]->report_endpoints[1]);
+}
+
+TEST(HTTPParsersTest, ParseContentSecurityPolicyReportUri) {
+  auto policies = ParseContentSecurityPolicy(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Security-Policy: report-uri ./report.py\r\n",
+      KURL("http://example.com"));
+  EXPECT_FALSE(policies[0]->use_reporting_api);
+  ASSERT_EQ(1u, policies[0]->report_endpoints.size());
+  EXPECT_EQ("http://example.com/report.py", policies[0]->report_endpoints[0]);
+}
+
+TEST(HTTPParsersTest, ParseContentSecurityPolicySourceBasic) {
+  auto frame_ancestors = network::mojom::CSPDirectiveName::FrameAncestors;
+  auto policies = ParseContentSecurityPolicy(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Security-Policy: frame-ancestors 'none'\r\n"
+      "Content-Security-Policy: frame-ancestors *\r\n"
+      "Content-Security-Policy: frame-ancestors 'self'\r\n"
+      "Content-Security-Policy: frame-ancestors http://a.com:22/path\r\n"
+      "Content-Security-Policy: frame-ancestors a.com:*\r\n"
+      "Content-Security-Policy: frame-ancestors */report.py\r\n",
+      KURL("http://example.com"));
+  // 'none'
+  {
+    auto source_list = policies[0]->directives.Take(frame_ancestors);
+    EXPECT_EQ(0u, source_list->sources.size());
+    EXPECT_FALSE(source_list->allow_self);
+    EXPECT_FALSE(source_list->allow_star);
+    EXPECT_FALSE(source_list->allow_response_redirects);
+  }
+
+  // *
+  {
+    auto source_list = policies[1]->directives.Take(frame_ancestors);
+    EXPECT_EQ(0u, source_list->sources.size());
+    EXPECT_FALSE(source_list->allow_self);
+    EXPECT_TRUE(source_list->allow_star);
+    EXPECT_FALSE(source_list->allow_response_redirects);
+  }
+
+  // 'self'
+  {
+    auto source_list = policies[2]->directives.Take(frame_ancestors);
+    EXPECT_EQ(0u, source_list->sources.size());
+    EXPECT_TRUE(source_list->allow_self);
+    EXPECT_FALSE(source_list->allow_star);
+    EXPECT_FALSE(source_list->allow_response_redirects);
+  }
+
+  // http://a.com:22/path
+  {
+    auto source_list = policies[3]->directives.Take(frame_ancestors);
+    EXPECT_FALSE(source_list->allow_self);
+    EXPECT_FALSE(source_list->allow_star);
+    EXPECT_FALSE(source_list->allow_response_redirects);
+    EXPECT_EQ(1u, source_list->sources.size());
+    auto& source = source_list->sources[0];
+    EXPECT_EQ("http", source->scheme);
+    EXPECT_EQ("a.com", source->host);
+    EXPECT_EQ("/path", source->path);
+    EXPECT_FALSE(source->is_host_wildcard);
+    EXPECT_FALSE(source->is_port_wildcard);
+  }
+
+  // a.com:*
+  {
+    auto source_list = policies[4]->directives.Take(frame_ancestors);
+    EXPECT_FALSE(source_list->allow_self);
+    EXPECT_FALSE(source_list->allow_star);
+    EXPECT_FALSE(source_list->allow_response_redirects);
+    EXPECT_EQ(1u, source_list->sources.size());
+    auto& source = source_list->sources[0];
+    EXPECT_EQ("", source->scheme);
+    EXPECT_EQ("a.com", source->host);
+    EXPECT_EQ("", source->path);
+    EXPECT_FALSE(source->is_host_wildcard);
+    EXPECT_TRUE(source->is_port_wildcard);
+  }
+
+  // frame-ancestors */report.py
+  {
+    auto source_list = policies[5]->directives.Take(frame_ancestors);
+    EXPECT_FALSE(source_list->allow_self);
+    EXPECT_FALSE(source_list->allow_star);
+    EXPECT_FALSE(source_list->allow_response_redirects);
+    EXPECT_EQ(1u, source_list->sources.size());
+    auto& source = source_list->sources[0];
+    EXPECT_EQ("", source->scheme);
+    EXPECT_EQ("", source->host);
+    EXPECT_EQ(-1, source->port);
+    EXPECT_EQ("/report.py", source->path);
+    EXPECT_TRUE(source->is_host_wildcard);
+    EXPECT_FALSE(source->is_port_wildcard);
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
index 5ee62b2f..857ea618 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
@@ -85,10 +85,11 @@
   info.has_trusted_rate_controller = enc_info.has_trusted_rate_controller;
   info.is_hardware_accelerated = enc_info.is_hardware_accelerated;
   info.supports_simulcast = enc_info.supports_simulcast;
-  // TODO(crbug.com/1034686): Copy ScalingSettings once getStats() hang issue
-  // is resolved.
-  // info.scaling_settings = webrtc::VideoEncoder::ScalingSettings(
-  // enc_info.scaling_settings.min_qp, enc_info.scaling_settings.max_qp);
+  info.scaling_settings = enc_info.scaling_settings
+                              ? webrtc::VideoEncoder::ScalingSettings(
+                                    enc_info.scaling_settings->min_qp,
+                                    enc_info.scaling_settings->max_qp)
+                              : webrtc::VideoEncoder::ScalingSettings::kOff;
   static_assert(
       webrtc::kMaxSpatialLayers >= media::VideoEncoderInfo::kMaxSpatialLayers,
       "webrtc::kMaxSpatiallayers is less than "
diff --git a/third_party/blink/renderer/platform/scheduler/BUILD.gn b/third_party/blink/renderer/platform/scheduler/BUILD.gn
index 9e1692a6..8cdf320 100644
--- a/third_party/blink/renderer/platform/scheduler/BUILD.gn
+++ b/third_party/blink/renderer/platform/scheduler/BUILD.gn
@@ -62,6 +62,7 @@
     "common/worker_pool.cc",
     "main_thread/agent_interference_recorder.cc",
     "main_thread/agent_interference_recorder.h",
+    "main_thread/attribution_group.h",
     "main_thread/auto_advancing_virtual_time_domain.cc",
     "main_thread/auto_advancing_virtual_time_domain.h",
     "main_thread/compositor_priority_experiments.cc",
@@ -126,7 +127,6 @@
     "public/frame_status.h",
     "public/page_lifecycle_state.h",
     "public/page_scheduler.h",
-    "public/pending_user_input_type.h",
     "public/post_cancellable_task.h",
     "public/post_cross_thread_task.h",
     "public/rail_mode_observer.h",
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/attribution_group.h b/third_party/blink/renderer/platform/scheduler/main_thread/attribution_group.h
new file mode 100644
index 0000000..4d5b3b0
--- /dev/null
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/attribution_group.h
@@ -0,0 +1,69 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_MAIN_THREAD_ATTRIBUTION_GROUP_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_MAIN_THREAD_ATTRIBUTION_GROUP_H_
+
+#include "third_party/blink/public/common/input/web_input_event_attribution.h"
+#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
+#include "third_party/blink/renderer/platform/wtf/hash_traits.h"
+
+namespace blink {
+namespace scheduler {
+
+// A hashable wrapper around a WebInputEventAttribution that can be used to
+// group pending events together.
+struct AttributionGroup {
+  explicit AttributionGroup(WebInputEventAttribution attribution)
+      : attribution(attribution) {}
+
+  explicit AttributionGroup(WTF::HashTableDeletedValueType)
+      : is_deleted_value(true) {}
+
+  AttributionGroup() = default;
+
+  bool IsHashTableDeletedValue() const { return is_deleted_value; }
+
+  bool operator==(const AttributionGroup& other) const {
+    return attribution == other.attribution &&
+           is_deleted_value == other.is_deleted_value;
+  }
+
+  WebInputEventAttribution attribution = {};
+  bool is_deleted_value = false;
+};
+
+struct AttributionGroupHash {
+  STATIC_ONLY(AttributionGroupHash);
+
+  static unsigned GetHash(const AttributionGroup& group) {
+    return group.attribution.GetHash();
+  }
+
+  static bool Equal(const AttributionGroup& a, const AttributionGroup& b) {
+    return a == b;
+  }
+
+  static const bool safe_to_compare_to_empty_or_deleted = true;
+};
+
+}  // namespace scheduler
+}  // namespace blink
+
+namespace WTF {
+
+template <>
+struct DefaultHash<blink::scheduler::AttributionGroup> {
+  using Hash = blink::scheduler::AttributionGroupHash;
+};
+
+template <>
+struct HashTraits<blink::scheduler::AttributionGroup>
+    : SimpleClassHashTraits<blink::scheduler::AttributionGroup> {
+  static const bool kEmptyValueIsZero = false;
+};
+
+}  // namespace WTF
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_MAIN_THREAD_ATTRIBUTION_GROUP_H_
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
index 08ce18f..c02159b20 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
@@ -1256,7 +1256,8 @@
     WebInputEvent::Type web_input_event_type,
     const WebInputEventAttribution& web_input_event_attribution) {
   base::AutoLock lock(any_thread_lock_);
-  any_thread().pending_input_monitor.OnEnqueue(web_input_event_type);
+  any_thread().pending_input_monitor.OnEnqueue(web_input_event_type,
+                                               web_input_event_attribution);
 }
 
 void MainThreadSchedulerImpl::WillHandleInputEventOnMainThread(
@@ -1265,7 +1266,8 @@
   helper_.CheckOnValidThread();
 
   base::AutoLock lock(any_thread_lock_);
-  any_thread().pending_input_monitor.OnDequeue(web_input_event_type);
+  any_thread().pending_input_monitor.OnDequeue(web_input_event_type,
+                                               web_input_event_attribution);
 }
 
 void MainThreadSchedulerImpl::DidHandleInputEventOnMainThread(
@@ -2286,9 +2288,11 @@
   main_thread_only().process_type = type;
 }
 
-PendingUserInputInfo MainThreadSchedulerImpl::GetPendingUserInputInfo() const {
+Vector<WebInputEventAttribution>
+MainThreadSchedulerImpl::GetPendingUserInputInfo(
+    bool include_continuous) const {
   base::AutoLock lock(any_thread_lock_);
-  return any_thread().pending_input_monitor.Info();
+  return any_thread().pending_input_monitor.Info(include_continuous);
 }
 
 bool MainThreadSchedulerImpl::IsBeginMainFrameScheduled() const {
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
index 88978fe..af5903d 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
@@ -209,7 +209,8 @@
   void AddRAILModeObserver(RAILModeObserver* observer) override;
   void RemoveRAILModeObserver(RAILModeObserver const* observer) override;
   void SetRendererProcessType(WebRendererProcessType type) override;
-  PendingUserInputInfo GetPendingUserInputInfo() const override;
+  Vector<WebInputEventAttribution> GetPendingUserInputInfo(
+      bool include_continuous) const override;
   bool IsBeginMainFrameScheduled() const override;
 
   // ThreadScheduler implementation:
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input.cc b/third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input.cc
index b25f49e1..afb4d36 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input.cc
@@ -3,126 +3,80 @@
 // found in the LICENSE file.
 
 #include "third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input.h"
-#include "third_party/blink/renderer/platform/wtf/hash_map.h"
-#include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
-#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
+
+#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 namespace scheduler {
 
-typedef HashMap<AtomicString, PendingUserInputType> PendingInputTypeMap;
-
-namespace {
-
-void PopulatePendingInputTypeMap(PendingInputTypeMap& map) {
-  map = {
-      {"click", PendingUserInputType::kClick},
-      {"dblclick", PendingUserInputType::kDblClick},
-      {"mousedown", PendingUserInputType::kMouseDown},
-      {"mouseenter", PendingUserInputType::kMouseEnter},
-      {"mouseleave", PendingUserInputType::kMouseLeave},
-      {"mousemove", PendingUserInputType::kMouseMove},
-      {"mouseout", PendingUserInputType::kMouseOut},
-      {"mouseover", PendingUserInputType::kMouseOver},
-      {"mouseup", PendingUserInputType::kMouseUp},
-      {"wheel", PendingUserInputType::kWheel},
-      {"keydown", PendingUserInputType::kKeyDown},
-      {"keyup", PendingUserInputType::kKeyUp},
-      {"touchstart", PendingUserInputType::kTouchStart},
-      {"touchend", PendingUserInputType::kTouchEnd},
-      {"touchmove", PendingUserInputType::kTouchMove},
-      {"touchcancel", PendingUserInputType::kTouchCancel},
-  };
-}
-
-}  // namespace
-
-void PendingUserInput::Monitor::OnEnqueue(WebInputEvent::Type type) {
+void PendingUserInput::Monitor::OnEnqueue(
+    WebInputEvent::Type type,
+    const WebInputEventAttribution& attribution) {
   DCHECK_NE(type, WebInputEvent::kUndefined);
   DCHECK_LE(type, WebInputEvent::kTypeLast);
 
-  size_t& counter = counters_[type];
-  counter++;
+  // Ignore events without attribution information.
+  if (attribution.type() == WebInputEventAttribution::kUnknown)
+    return;
+
+  auto result =
+      pending_events_.insert(AttributionGroup(attribution), EventCounter());
+  auto& value = result.stored_value->value;
+  if (IsContinuousEventType(type)) {
+    value.num_continuous++;
+  } else {
+    value.num_discrete++;
+  }
 }
 
-void PendingUserInput::Monitor::OnDequeue(WebInputEvent::Type type) {
+void PendingUserInput::Monitor::OnDequeue(
+    WebInputEvent::Type type,
+    const WebInputEventAttribution& attribution) {
   DCHECK_NE(type, WebInputEvent::kUndefined);
   DCHECK_LE(type, WebInputEvent::kTypeLast);
 
-  size_t& counter = counters_[type];
-  DCHECK_GT(counter, size_t{0});
-  counter--;
-}
+  if (attribution.type() == WebInputEventAttribution::kUnknown)
+    return;
 
-PendingUserInputInfo PendingUserInput::Monitor::Info() const {
-  PendingUserInputType mask = PendingUserInputType::kNone;
-  for (int type = WebInputEvent::kTypeFirst + 1;
-       type <= WebInputEvent::kTypeLast; type++) {
-    const size_t& counter = counters_[type];
-    DCHECK_GE(counter, size_t{0});
-    if (counter == 0)
-      continue;
+  auto it = pending_events_.find(AttributionGroup(attribution));
+  DCHECK_NE(it, pending_events_.end());
 
-    mask = mask |
-           TypeFromWebInputEventType(static_cast<WebInputEvent::Type>(type));
-  }
-  return PendingUserInputInfo(mask);
-}
-
-// static
-PendingUserInputType PendingUserInput::TypeFromString(
-    const AtomicString& pending_user_input_type) {
-  DEFINE_STATIC_LOCAL(PendingInputTypeMap, kPendingInputTypeMap, ());
-  if (kPendingInputTypeMap.IsEmpty()) {
-    PopulatePendingInputTypeMap(kPendingInputTypeMap);
-    DCHECK(!kPendingInputTypeMap.IsEmpty());
+  auto& value = it->value;
+  if (IsContinuousEventType(type)) {
+    DCHECK_GT(value.num_continuous, 0U);
+    value.num_continuous--;
+  } else {
+    DCHECK_GT(value.num_discrete, 0U);
+    value.num_discrete--;
   }
 
-  const auto type_iter = kPendingInputTypeMap.find(pending_user_input_type);
-  if (type_iter != kPendingInputTypeMap.end())
-    return type_iter->value;
-  return PendingUserInputType::kNone;
+  if (value.num_continuous == 0 && value.num_discrete == 0) {
+    pending_events_.erase(it->key);
+  }
 }
 
-// static
-PendingUserInputType PendingUserInput::TypeFromWebInputEventType(
-    WebInputEvent::Type type) {
-  using Type = PendingUserInputType;
+Vector<WebInputEventAttribution> PendingUserInput::Monitor::Info(
+    bool include_continuous) const {
+  Vector<WebInputEventAttribution> attributions;
+  for (const auto& entry : pending_events_) {
+    if (entry.value.num_discrete > 0 ||
+        (entry.value.num_continuous > 0 && include_continuous)) {
+      attributions.push_back(entry.key.attribution);
+    }
+  }
+  return attributions;
+}
+
+bool PendingUserInput::IsContinuousEventType(WebInputEvent::Type type) {
   switch (type) {
-    case WebInputEvent::Type::kMouseDown:
-      return Type::kMouseDown;
-    case WebInputEvent::Type::kMouseUp:
-      return Type::kClick | Type::kDblClick | Type::kMouseUp;
-    case WebInputEvent::Type::kMouseMove:
-      return Type::kMouseMove;
-    case WebInputEvent::Type::kMouseEnter:
-      return Type::kMouseEnter;
-    case WebInputEvent::Type::kMouseLeave:
-      return Type::kMouseLeave;
-    case WebInputEvent::Type::kMouseWheel:
-      return Type::kWheel;
-
-    case WebInputEvent::Type::kKeyDown:
-    case WebInputEvent::Type::kRawKeyDown:
-    case WebInputEvent::Type::kChar:
-      return Type::kKeyDown;
-    case WebInputEvent::Type::kKeyUp:
-      return Type::kKeyUp;
-
-    case WebInputEvent::Type::kTouchStart:
-    case WebInputEvent::Type::kGestureTapDown:
-      return Type::kTouchStart | Type::kMouseDown;
-    case WebInputEvent::Type::kTouchMove:
-      return Type::kTouchMove;
-    case WebInputEvent::Type::kTouchEnd:
-    case WebInputEvent::Type::kGestureTap:
-      return Type::kTouchEnd | Type::kClick | Type::kDblClick | Type::kMouseUp;
-    case WebInputEvent::Type::kTouchCancel:
-    case WebInputEvent::Type::kGestureTapCancel:
-      return Type::kTouchCancel;
-
+    case WebInputEvent::kMouseMove:
+    case WebInputEvent::kMouseWheel:
+    case WebInputEvent::kTouchMove:
+    case WebInputEvent::kPointerMove:
+    case WebInputEvent::kPointerRawUpdate:
+      return true;
     default:
-      return Type::kNone;
+      return false;
   }
 }
 
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input.h b/third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input.h
index 6249821..4c4863c 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input.h
@@ -5,11 +5,12 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_MAIN_THREAD_PENDING_USER_INPUT_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_MAIN_THREAD_PENDING_USER_INPUT_H_
 
-#include <array>
-
 #include "third_party/blink/public/common/input/web_input_event.h"
-#include "third_party/blink/renderer/platform/scheduler/public/pending_user_input_type.h"
-#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+#include "third_party/blink/public/common/input/web_input_event_attribution.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/scheduler/main_thread/attribution_group.h"
+#include "third_party/blink/renderer/platform/wtf/hash_map.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 namespace scheduler {
@@ -24,22 +25,36 @@
     DISALLOW_NEW();
 
    public:
-    Monitor() { this->counters_.fill(0); }
-    void OnEnqueue(WebInputEvent::Type);
-    void OnDequeue(WebInputEvent::Type);
+    Monitor() = default;
 
-    PendingUserInputInfo Info() const;
+    void OnEnqueue(WebInputEvent::Type, const WebInputEventAttribution&);
+    void OnDequeue(WebInputEvent::Type, const WebInputEventAttribution&);
+
+    // Returns a list of all unique attributions that are marked for event
+    // dispatch. If |include_continuous| is true, include event types from
+    // "continuous" sources (see PendingUserInput::IsContinuousEventTypes).
+    Vector<WebInputEventAttribution> Info(bool include_continuous) const;
 
    private:
-    std::array<size_t, WebInputEvent::kTypeLast + 1> counters_;
+    struct EventCounter {
+      EventCounter() = default;
+
+      size_t num_discrete = 0;
+      size_t num_continuous = 0;
+    };
+
+    // A mapping between attributions to pending events.
+    HashMap<AttributionGroup, EventCounter> pending_events_;
 
     DISALLOW_COPY_AND_ASSIGN(Monitor);
   };
 
   PendingUserInput() = delete;
 
-  static PendingUserInputType TypeFromString(const AtomicString&);
-  static PendingUserInputType TypeFromWebInputEventType(WebInputEvent::Type);
+  // Returns true if the given blink event type is considered to be sampled
+  // from a continuous source.
+  // https://wicg.github.io/is-input-pending/#continuousevents
+  static bool IsContinuousEventType(WebInputEvent::Type);
 
   DISALLOW_COPY_AND_ASSIGN(PendingUserInput);
 };
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input_unittest.cc
index 8d07214..0d96a4a 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input_unittest.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "third_party/blink/renderer/platform/scheduler/main_thread/pending_user_input.h"
-#include "third_party/blink/renderer/platform/scheduler/public/pending_user_input_type.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -15,85 +14,71 @@
   PendingUserInput::Monitor monitor_;
 };
 
-// Tests that a single event type is tracked.
-TEST_F(PendingUserInputMonitorTest, TestCounterBasic) {
-  EXPECT_FALSE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_FALSE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
-
-  monitor_.OnEnqueue(WebInputEvent::kMouseDown);
-  EXPECT_TRUE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_TRUE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
-
-  monitor_.OnDequeue(WebInputEvent::kMouseDown);
-  EXPECT_FALSE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_FALSE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
+// Sanity check for discrete/continuous queues.
+TEST_F(PendingUserInputMonitorTest, QueuingSimple) {
+  monitor_.OnEnqueue(WebInputEvent::kMouseDown, {});
+  monitor_.OnEnqueue(WebInputEvent::kMouseMove, {});
+  monitor_.OnEnqueue(WebInputEvent::kMouseUp, {});
+  monitor_.OnDequeue(WebInputEvent::kMouseDown, {});
+  monitor_.OnDequeue(WebInputEvent::kMouseMove, {});
+  monitor_.OnDequeue(WebInputEvent::kMouseUp, {});
 }
 
-// Tests that enqueuing multiple identical event types is tracked correctly.
-TEST_F(PendingUserInputMonitorTest, TestCounterNested) {
-  EXPECT_FALSE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_FALSE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
+// Basic test of continuous and discrete event detection.
+TEST_F(PendingUserInputMonitorTest, EventDetection) {
+  WebInputEventAttribution focus(WebInputEventAttribution::kFocusedFrame);
+  WebInputEventAttribution frame(WebInputEventAttribution::kTargetedFrame,
+                                 cc::ElementId(0xDEADBEEF));
 
-  monitor_.OnEnqueue(WebInputEvent::kMouseDown);
-  EXPECT_TRUE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_TRUE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
+  EXPECT_EQ(monitor_.Info(false).size(), 0U);
+  EXPECT_EQ(monitor_.Info(true).size(), 0U);
 
-  monitor_.OnEnqueue(WebInputEvent::kMouseDown);
-  EXPECT_TRUE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_TRUE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
+  // Verify that an event with invalid attribution is ignored.
+  monitor_.OnEnqueue(WebInputEvent::kKeyDown, {});
+  EXPECT_EQ(monitor_.Info(false).size(), 0U);
+  EXPECT_EQ(monitor_.Info(true).size(), 0U);
 
-  monitor_.OnDequeue(WebInputEvent::kMouseDown);
-  EXPECT_TRUE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_TRUE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
+  // Discrete events with a unique attribution should increment the attribution
+  // count.
+  monitor_.OnEnqueue(WebInputEvent::kMouseDown, focus);
+  EXPECT_EQ(monitor_.Info(false).size(), 1U);
+  EXPECT_EQ(monitor_.Info(true).size(), 1U);
 
-  monitor_.OnDequeue(WebInputEvent::kMouseDown);
-  EXPECT_FALSE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_FALSE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
-}
+  // Multiple enqueued events with the same attribution target should not
+  // return the attribution twice.
+  monitor_.OnEnqueue(WebInputEvent::kMouseUp, focus);
+  EXPECT_EQ(monitor_.Info(false).size(), 1U);
+  EXPECT_EQ(monitor_.Info(true).size(), 1U);
 
-// Tests that non-overlapping input types are tracked independently.
-TEST_F(PendingUserInputMonitorTest, TestCounterDisjoint) {
-  EXPECT_FALSE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_FALSE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseUp));
-  EXPECT_FALSE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
+  // Events with new attribution information should return a new attribution
+  // (in this case, continuous).
+  monitor_.OnEnqueue(WebInputEvent::kMouseMove, frame);
+  EXPECT_EQ(monitor_.Info(false).size(), 1U);
+  EXPECT_EQ(monitor_.Info(true).size(), 2U);
 
-  monitor_.OnEnqueue(WebInputEvent::kMouseDown);
-  EXPECT_TRUE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_FALSE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseUp));
-  EXPECT_TRUE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
+  monitor_.OnEnqueue(WebInputEvent::kKeyDown, frame);
+  EXPECT_EQ(monitor_.Info(false).size(), 2U);
+  EXPECT_EQ(monitor_.Info(true).size(), 2U);
 
-  monitor_.OnEnqueue(WebInputEvent::kMouseUp);
-  EXPECT_TRUE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_TRUE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseUp));
-  EXPECT_TRUE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
+  monitor_.OnDequeue(WebInputEvent::kKeyDown, {});
+  EXPECT_EQ(monitor_.Info(false).size(), 2U);
+  EXPECT_EQ(monitor_.Info(true).size(), 2U);
 
-  monitor_.OnDequeue(WebInputEvent::kMouseDown);
-  EXPECT_FALSE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_TRUE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseUp));
-  EXPECT_TRUE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
+  monitor_.OnDequeue(WebInputEvent::kMouseDown, focus);
+  EXPECT_EQ(monitor_.Info(false).size(), 2U);
+  EXPECT_EQ(monitor_.Info(true).size(), 2U);
 
-  monitor_.OnDequeue(WebInputEvent::kMouseUp);
-  EXPECT_FALSE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseDown));
-  EXPECT_FALSE(
-      monitor_.Info().HasPendingInputType(PendingUserInputType::kMouseUp));
-  EXPECT_FALSE(monitor_.Info().HasPendingInputType(PendingUserInputType::kAny));
+  monitor_.OnDequeue(WebInputEvent::kMouseUp, focus);
+  EXPECT_EQ(monitor_.Info(false).size(), 1U);
+  EXPECT_EQ(monitor_.Info(true).size(), 1U);
+
+  monitor_.OnDequeue(WebInputEvent::kMouseMove, frame);
+  EXPECT_EQ(monitor_.Info(false).size(), 1U);
+  EXPECT_EQ(monitor_.Info(true).size(), 1U);
+
+  monitor_.OnDequeue(WebInputEvent::kKeyDown, frame);
+  EXPECT_EQ(monitor_.Info(false).size(), 0U);
+  EXPECT_EQ(monitor_.Info(true).size(), 0U);
 }
 
 }  // namespace scheduler
diff --git a/third_party/blink/renderer/platform/scheduler/public/pending_user_input_type.h b/third_party/blink/renderer/platform/scheduler/public/pending_user_input_type.h
deleted file mode 100644
index ab1b52d..0000000
--- a/third_party/blink/renderer/platform/scheduler/public/pending_user_input_type.h
+++ /dev/null
@@ -1,80 +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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_PENDING_USER_INPUT_TYPE_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_PENDING_USER_INPUT_TYPE_H_
-
-#include "third_party/blink/renderer/platform/platform_export.h"
-#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
-
-namespace blink {
-namespace scheduler {
-
-// A subset of DOM input events that should be supported by
-// hasPendingUserInput. Each one of these maps to one or more
-// WebInputEvent::Type items.
-enum class PendingUserInputType {
-  kNone = 0,
-
-  kClick = 1 << 1,
-  kDblClick = 1 << 2,
-  kMouseDown = 1 << 3,
-  kMouseEnter = 1 << 4,
-  kMouseLeave = 1 << 5,
-  kMouseMove = 1 << 6,
-  kMouseOut = 1 << 7,
-  kMouseOver = 1 << 8,
-  kMouseUp = 1 << 9,
-
-  kWheel = 1 << 10,
-
-  kKeyDown = 1 << 11,
-  kKeyUp = 1 << 12,
-
-  kTouchStart = 1 << 13,
-  kTouchEnd = 1 << 14,
-  kTouchMove = 1 << 15,
-  kTouchCancel = 1 << 16,
-
-  kAny = ~0  // Wildcard input event type
-};
-
-inline constexpr PendingUserInputType operator&(PendingUserInputType a,
-                                                PendingUserInputType b) {
-  return static_cast<PendingUserInputType>(static_cast<int>(a) &
-                                           static_cast<int>(b));
-}
-
-inline constexpr PendingUserInputType operator|(PendingUserInputType a,
-                                                PendingUserInputType b) {
-  return static_cast<PendingUserInputType>(static_cast<int>(a) |
-                                           static_cast<int>(b));
-}
-
-inline constexpr bool operator==(PendingUserInputType a,
-                                 PendingUserInputType b) {
-  return static_cast<int>(a) == static_cast<int>(b);
-}
-
-// A wrapper around a set of input types that have been flagged as pending.
-class PLATFORM_EXPORT PendingUserInputInfo {
-  DISALLOW_NEW();
-
- public:
-  constexpr explicit PendingUserInputInfo(PendingUserInputType mask)
-      : mask_(mask) {}
-  constexpr PendingUserInputInfo() : mask_(PendingUserInputType::kNone) {}
-
-  constexpr bool HasPendingInputType(PendingUserInputType type) const {
-    return (mask_ & type) != PendingUserInputType::kNone;
-  }
-
- private:
-  const PendingUserInputType mask_;
-};
-
-}  // namespace scheduler
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_PENDING_USER_INPUT_TYPE_H_
diff --git a/third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h b/third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h
index 20e3e743..3979207 100644
--- a/third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h
+++ b/third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h
@@ -10,10 +10,11 @@
 #include "base/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
+#include "third_party/blink/public/common/input/web_input_event_attribution.h"
 #include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/page_scheduler.h"
-#include "third_party/blink/renderer/platform/scheduler/public/pending_user_input_type.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace v8 {
 class Isolate;
@@ -132,8 +133,12 @@
   virtual void AddTaskObserver(base::TaskObserver* task_observer) = 0;
   virtual void RemoveTaskObserver(base::TaskObserver* task_observer) = 0;
 
-  virtual scheduler::PendingUserInputInfo GetPendingUserInputInfo() const {
-    return scheduler::PendingUserInputInfo();
+  // Returns a list of all unique attributions that are marked for event
+  // dispatch. If |include_continuous| is true, include event types from
+  // "continuous" sources (see PendingUserInput::IsContinuousEventTypes).
+  virtual Vector<WebInputEventAttribution> GetPendingUserInputInfo(
+      bool include_continuous) const {
+    return {};
   }
 
   // Indicates that a BeginMainFrame task has been scheduled to run on the main
diff --git a/third_party/blink/renderer/platform/wtf/hash_map.h b/third_party/blink/renderer/platform/wtf/hash_map.h
index 88fc20f..db9948d 100644
--- a/third_party/blink/renderer/platform/wtf/hash_map.h
+++ b/third_party/blink/renderer/platform/wtf/hash_map.h
@@ -193,18 +193,6 @@
   template <typename HashTranslator, typename T>
   bool Contains(const T&) const;
 
-  // An alternate version of insert() that finds the object by hashing and
-  // comparing with some other type, to avoid the cost of type conversion if
-  // the object is already in the table. HashTranslator must have the
-  // following function members:
-  //   static unsigned hash(const T&);
-  //   static bool equal(const ValueType&, const T&);
-  //   static translate(ValueType&, const T&, unsigned hashCode);
-  template <typename HashTranslator,
-            typename IncomingKeyType,
-            typename IncomingMappedType>
-  AddResult Insert(IncomingKeyType&&, IncomingMappedType&&);
-
   template <typename IncomingKeyType>
   static bool IsValidKey(const IncomingKeyType&);
 
@@ -583,24 +571,6 @@
           typename W,
           typename X,
           typename Y>
-template <typename HashTranslator,
-          typename IncomingKeyType,
-          typename IncomingMappedType>
-auto HashMap<T, U, V, W, X, Y>::Insert(IncomingKeyType&& key,
-                                       IncomingMappedType&& mapped)
-    -> AddResult {
-  return impl_.template AddPassingHashCode<
-      HashMapTranslatorAdapter<ValueTraits, HashTranslator>>(
-      std::forward<IncomingKeyType>(key),
-      std::forward<IncomingMappedType>(mapped));
-}
-
-template <typename T,
-          typename U,
-          typename V,
-          typename W,
-          typename X,
-          typename Y>
 template <typename IncomingKeyType, typename IncomingMappedType>
 typename HashMap<T, U, V, W, X, Y>::AddResult HashMap<T, U, V, W, X, Y>::insert(
     IncomingKeyType&& key,
diff --git a/third_party/blink/renderer/platform/wtf/linked_hash_set.h b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
index 0c4308e3..abf2f56 100644
--- a/third_party/blink/renderer/platform/wtf/linked_hash_set.h
+++ b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
@@ -55,7 +55,7 @@
 template <typename LinkedHashSet>
 class LinkedHashSetConstReverseIterator;
 
-template <typename Value, typename HashFunctions>
+template <typename Value, typename HashFunctions, typename TraitsArg>
 struct LinkedHashSetTranslator;
 template <typename Value>
 struct LinkedHashSetExtractor;
@@ -216,7 +216,8 @@
   typedef TraitsArg Traits;
   typedef LinkedHashSetNode<Value> Node;
   typedef LinkedHashSetNodeBase NodeBase;
-  typedef LinkedHashSetTranslator<Value, HashFunctions> NodeHashFunctions;
+  typedef LinkedHashSetTranslator<Value, HashFunctions, Traits>
+      NodeHashFunctions;
   typedef LinkedHashSetTraits<Value, Traits, Allocator> NodeHashTraits;
 
   typedef HashTable<Node,
@@ -250,7 +251,7 @@
     bool is_new_entry;
   };
 
-  typedef typename HashTraits<Value>::PeekInType ValuePeekInType;
+  typedef typename TraitsArg::PeekInType ValuePeekInType;
 
   LinkedHashSet();
   LinkedHashSet(const LinkedHashSet&);
@@ -402,12 +403,12 @@
   NodeBase anchor_;
 };
 
-template <typename Value, typename HashFunctions>
+template <typename Value, typename HashFunctions, typename TraitsArg>
 struct LinkedHashSetTranslator {
   STATIC_ONLY(LinkedHashSetTranslator);
   typedef LinkedHashSetNode<Value> Node;
   typedef LinkedHashSetNodeBase NodeBase;
-  typedef typename HashTraits<Value>::PeekInType ValuePeekInType;
+  typedef typename TraitsArg::PeekInType ValuePeekInType;
   static unsigned GetHash(const Node& node) {
     return HashFunctions::GetHash(node.value_);
   }
diff --git a/third_party/blink/web_tests/SmokeTests b/third_party/blink/web_tests/SmokeTests
index b615982b..096c1e53 100644
--- a/third_party/blink/web_tests/SmokeTests
+++ b/third_party/blink/web_tests/SmokeTests
@@ -45,7 +45,6 @@
 css3/device-adapt/viewport-insert-rule-after.html
 css3/device-adapt/viewport-insert-rule-before.html
 css3/filters/crash-hw-sw-switch.html
-css3/filters/multiple-references-id-mutate-crash.html
 css3/flexbox/assert-generated-new-flexbox.html
 css3/fonts/font-feature-settings-parsing-prefixed.html
 css3/masking/clip-path-reference-of-fake-clipPath.html
@@ -164,6 +163,7 @@
 external/wpt/css/css-typed-om/declared-styleMap-accepts-inherit.html
 external/wpt/css/css-typed-om/stylevalue-subclasses/cssKeywordValue.html
 external/wpt/css/css-typed-om/the-stylepropertymap/properties/border-style.html
+external/wpt/css/filter-effects/crashtests/multiple-references-id-crash-001.html
 external/wpt/custom-elements/HTMLElement-constructor.html
 external/wpt/custom-elements/parser/parser-constructs-custom-element-in-document-write.html
 external/wpt/custom-elements/parser/parser-constructs-custom-elements.html
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index b92cac0..f04ac63 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -272,9 +272,9 @@
 crbug.com/968791 virtual/scalefactor200/css3/filters/filterRegions.html [ Failure ]
 
 # These appear to be actually incorrect at device_scale_factor 2.0:
-crbug.com/968791 crbug.com/1051044 virtual/scalefactor200/css3/filters/effect-reference-image-lazy-attach.html [ Failure ]
 crbug.com/968791 crbug.com/1051044 virtual/scalefactor200/external/wpt/css/filter-effects/effect-reference-feimage-001.html [ Failure ]
 crbug.com/968791 crbug.com/1051044 virtual/scalefactor200/external/wpt/css/filter-effects/effect-reference-feimage-002.html [ Failure ]
+crbug.com/968791 crbug.com/1051044 virtual/scalefactor200/external/wpt/css/filter-effects/effect-reference-feimage-003.html [ Failure ]
 crbug.com/968791 virtual/scalefactor200/external/wpt/css/filter-effects/filters-test-brightness-003.html [ Failure ]
 
 # Copying these from elsewhere in TestExpectations (for non-scalefactor200 virtual suite)
@@ -805,6 +805,16 @@
 crbug.com/1066577 external/wpt/css/css-lists/li-value-reversed-004.html [ Failure ]
 crbug.com/1066577 external/wpt/css/css-lists/li-value-reversed-003.html [ Failure ]
 
+# Comparing variable font rendering to static font rendering fails on systems that use FreeType for variable fonts
+crbug.com/1067242 [ Win7 ] external/wpt/css/css-text-decor/text-underline-position-from-font-variable.html [ Failure ]
+crbug.com/1067242 [ Mac10.10 ] external/wpt/css/css-text-decor/text-underline-position-from-font-variable.html [ Failure ]
+crbug.com/1067242 [ Mac10.11 ] external/wpt/css/css-text-decor/text-underline-position-from-font-variable.html [ Failure ]
+crbug.com/1067242 [ Mac10.12 ] external/wpt/css/css-text-decor/text-underline-position-from-font-variable.html [ Failure ]
+crbug.com/1067242 [ Mac10.13 ] external/wpt/css/css-text-decor/text-underline-position-from-font-variable.html [ Failure ]
+crbug.com/1067242 [ Retina ] external/wpt/css/css-text-decor/text-underline-position-from-font-variable.html [ Failure ]
+# Windows 10 bots are not on high enough a Windows version to render variable fonts in DirectWrite
+crbug.com/1068947 [ Win10 ] external/wpt/css/css-text-decor/text-underline-position-from-font-variable.html [ Failure ]
+
 # ====== Layout team owned tests to here ======
 
 # ====== LayoutNG-only failures from here ======
@@ -2324,7 +2334,7 @@
 crbug.com/377696 printing/setPrinting.html [ Failure ]
 
 crbug.com/1051044 external/wpt/css/filter-effects/effect-reference-feimage-001.html [ Pass Failure ]
-crbug.com/1051044 css3/filters/effect-reference-image-lazy-attach.html [ Pass Failure ]
+crbug.com/1051044 external/wpt/css/filter-effects/effect-reference-feimage-003.html [ Pass Failure ]
 
 crbug.com/524160 [ Debug ] http/tests/media/media-source/stream_memory_tests/mediasource-appendbuffer-quota-exceeded-default-buffers.html [ Timeout ]
 crbug.com/524160 [ Debug ] virtual/audio-service/http/tests/media/media-source/stream_memory_tests/mediasource-appendbuffer-quota-exceeded-default-buffers.html [ Timeout ]
@@ -6558,6 +6568,9 @@
 crbug.com/1042877 virtual/autoupgrade-optionally-blockable-mixed-content/http/tests/mixed-autoupgrade/optionally/image-upgrade-console-message.https.html [ Failure ]
 crbug.com/1042877 virtual/autoupgrade-optionally-blockable-mixed-content/http/tests/mixed-autoupgrade/optionally/image-upgrade.https.html [ Failure ]
 
+# V8 roll
+crbug.com/v8/6751 http/tests/devtools/console/console-format-es6.js [ Skip ]
+
 # DevTools roll
 crbug.com/1006759 http/tests/devtools/profiler/agents-disabled-check.js [ Skip ]
 
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-displacement-negative-scale.html b/third_party/blink/web_tests/css3/filters/effect-reference-displacement-negative-scale.html
deleted file mode 100644
index 31c0477..0000000
--- a/third_party/blink/web_tests/css3/filters/effect-reference-displacement-negative-scale.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!DOCTYPE html>
-<div style="position: relative">
-  <div style="position: absolute; top: 20px; left: 20px; width: 100px; height: 100px; background: green; filter: url(#displacementFilter);"></div>
-</div>
-<svg width="0" height="0">
-  <filter id="displacementFilter" x="-0.2" y="-0.2" color-interpolation-filters="sRGB">
-    <feFlood flood-color="black"/>
-    <feDisplacementMap in="SourceGraphic" scale="-40" xChannelSelector="R" yChannelSelector="R"/>
-  </filter>
-</svg>
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-external-stylesheet-expected.html b/third_party/blink/web_tests/css3/filters/effect-reference-external-stylesheet-expected.html
deleted file mode 100644
index 49b6bb2..0000000
--- a/third_party/blink/web_tests/css3/filters/effect-reference-external-stylesheet-expected.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<!DOCTYPE html>
-<style>
-img {
-    margin: 10px;
-}
-</style>
-<img style="filter: url(resources/hueRotate.svg#MyFilter)" src="resources/reference.png">
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-external-stylesheet.html b/third_party/blink/web_tests/css3/filters/effect-reference-external-stylesheet.html
deleted file mode 100644
index cba3b11..0000000
--- a/third_party/blink/web_tests/css3/filters/effect-reference-external-stylesheet.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<!DOCTYPE html>
-<link rel="stylesheet" href="resources/filters.css"
-      onload="document.body && document.body.offsetTop">
-<style>
-img {
-    margin: 10px;
-}
-</style>
-<img class="huerotate180" src="resources/reference.png">
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-image-lazy-attach-expected.html b/third_party/blink/web_tests/css3/filters/effect-reference-image-lazy-attach-expected.html
deleted file mode 100644
index f8ddbf1..0000000
--- a/third_party/blink/web_tests/css3/filters/effect-reference-image-lazy-attach-expected.html
+++ /dev/null
@@ -1 +0,0 @@
-<img src="resources/reference.png">
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-image-lazy-attach.html b/third_party/blink/web_tests/css3/filters/effect-reference-image-lazy-attach.html
deleted file mode 100644
index fdebf13..0000000
--- a/third_party/blink/web_tests/css3/filters/effect-reference-image-lazy-attach.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<body>
-
-<style>
-  #filtered {
-    width: 160px;
-    height: 90px;
-    filter: url(#imagereplace);
-  }
-</style>
-
-<script type="text/html" id="source">
-    <div id="filtered"></div>
-    <svg xmlns="http://www.w3.org/3000/svg" width="0" height="0" xmlns:xlink="http://www.w3.org/1999/xlink">
-      <filter id="imagereplace" x="0%" y="0" width="100%" height="100%">
-         <feimage xlink:href="resources/reference.png"/>
-      </filter>
-    </svg>
-</script>
-
-<script>
-    document.body.offsetLeft;
-    var div = document.createElement('div');
-    div.innerHTML = document.getElementById('source').textContent;
-    document.body.appendChild(div);
-</script>
-
-</body>
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-local-url-with-base.html b/third_party/blink/web_tests/css3/filters/effect-reference-local-url-with-base.html
deleted file mode 100644
index c928cbe..0000000
--- a/third_party/blink/web_tests/css3/filters/effect-reference-local-url-with-base.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<base href="http://www.example.com/">
-<style>
-#target {
-  width: 100px;
-  height: 100px;
-  background-color: red;
-  filter: url(#filter);
-}
-</style>
-<div id="target"></div>
-<svg>
-  <filter id="filter" x="0" y="0" width="1" height="1" color-interpolation-filters="sRGB">
-    <feFlood flood-color="green"/>
-  </filter>
-</svg>
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-on-transparent-element-expected.html b/third_party/blink/web_tests/css3/filters/effect-reference-on-transparent-element-expected.html
deleted file mode 100644
index 0b57bc71..0000000
--- a/third_party/blink/web_tests/css3/filters/effect-reference-on-transparent-element-expected.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<style>
-.box {
-    width: 100px;
-    height: 100px;
-    background-color: rgb(0, 255, 0);
-}
-</style>
-<div class="box"></div>
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-on-transparent-element.html b/third_party/blink/web_tests/css3/filters/effect-reference-on-transparent-element.html
deleted file mode 100644
index 2cc3765..0000000
--- a/third_party/blink/web_tests/css3/filters/effect-reference-on-transparent-element.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<style>
-.box {
-    width: 100px;
-    height: 100px;
-    filter: url(#flood_green);
-}
-</style>
-<div class="box"></div>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0">
-    <defs>
-        <filter id="flood_green" x="0%" y="0%" width="100%" height="100%" color-interpolation-filters="sRGB">
-            <feColorMatrix type="matrix" values="0 0 0 0 0
-                                                 0 0 0 0 1
-                                                 0 0 0 0 0
-                                                 0 0 0 0 1"/>
-        </filter>
-    </defs>
-</svg>
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-source-alpha-hw.html b/third_party/blink/web_tests/css3/filters/effect-reference-source-alpha-hw.html
deleted file mode 100644
index c711f09..0000000
--- a/third_party/blink/web_tests/css3/filters/effect-reference-source-alpha-hw.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" version="1.1">
-  <defs>
-    <filter id="alpha" color-interpolation-filters="sRGB">
-      <feColorMatrix in="SourceAlpha"/>
-    </filter>
-  </defs>
-</svg>
-<style>
-.rect {
-    background-color: red;
-    width: 100px;
-    height: 100px;
-    filter: url(#alpha);
-    transform: translateZ(0);
-}
-</style>
-<div class="rect"></div>
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-source-alpha-not-first-expected.html b/third_party/blink/web_tests/css3/filters/effect-reference-source-alpha-not-first-expected.html
deleted file mode 100644
index f718ea6..0000000
--- a/third_party/blink/web_tests/css3/filters/effect-reference-source-alpha-not-first-expected.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<!DOCTYPE html>
-<div style="width: 100px; height: 100px; background-color: green"></div>
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-source-alpha-not-first.html b/third_party/blink/web_tests/css3/filters/effect-reference-source-alpha-not-first.html
deleted file mode 100644
index 0bdb803..0000000
--- a/third_party/blink/web_tests/css3/filters/effect-reference-source-alpha-not-first.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<style>
-div {
-  position: absolute;
-  width: 100px;
-  height: 100px;
-}
-#test {
-  background-color: red;
-  filter: opacity(0) url(#f);
-}
-</style>
-<div style="background-color: green"></div>
-<div id="test"></test>
-<svg height="0">
-  <filter id="f">
-    <feMerge><feMergeNode in="SourceAlpha"></feMergeNode></feMerge>
-  </filter>
-</svg>
diff --git a/third_party/blink/web_tests/css3/filters/multiple-references-id-mutate-crash-expected.txt b/third_party/blink/web_tests/css3/filters/multiple-references-id-mutate-crash-expected.txt
deleted file mode 100644
index ddad5eaf..0000000
--- a/third_party/blink/web_tests/css3/filters/multiple-references-id-mutate-crash-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-PASS if no crash
diff --git a/third_party/blink/web_tests/css3/filters/multiple-references-id-mutate-crash.html b/third_party/blink/web_tests/css3/filters/multiple-references-id-mutate-crash.html
deleted file mode 100644
index b5f35a51..0000000
--- a/third_party/blink/web_tests/css3/filters/multiple-references-id-mutate-crash.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<style>
-.filter {
-  filter: url(#foo);
-}
-</style>
-<div class="filter"></div>
-<div class="filter"></div>
-<button form="bar"></button>
-<script>
-if (window.testRunner)
-  testRunner.dumpAsText();
-onload = function() {
-  document.body.innerHTML = "PASS if no crash";
-}
-</script>
diff --git a/third_party/blink/web_tests/css3/flexbox/overflow-auto-resizes-correctly.html b/third_party/blink/web_tests/css3/flexbox/overflow-auto-resizes-correctly.html
index 865341fe..3998cf74 100644
--- a/third_party/blink/web_tests/css3/flexbox/overflow-auto-resizes-correctly.html
+++ b/third_party/blink/web_tests/css3/flexbox/overflow-auto-resizes-correctly.html
@@ -1,24 +1,14 @@
 <!DOCTYPE html>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
-<title>When a block inside a flexbox adds scrollbars due to overflow, the parent flexbox should re-flex based on the child size including scrollbars.</title>
+<title>When a block inside an horizontal flexbox adds vertical scrollbars due to overflow, the parent flexbox should re-flex based on the child size including scrollbars.</title>
 <style>
-.vflex {
-  display: flex;
-  flex-direction: column;
-  max-width: 200px;
-  margin: 10px 0 10px 0;
-}
 .hflex {
   display: flex;
   flex-direction: row;
   max-height: 200px;
   margin: 10px 0 10px 0;
 }
-.hbox {
-  overflow-x: auto;
-  white-space: nowrap;
-}
 .vbox {
   overflow-y: auto;
   max-height: 200px;
@@ -31,13 +21,6 @@
 }
 </style>
 
-<div class="vflex">
-  <div class="hbox">
-    <div class="rect"></div>
-    <div class="rect"></div>
-  </div>
-</div>
-
 <div class="hflex">
   <div class="vbox">
     <div class="rect" style="min-height: 300px;"></div>
@@ -56,12 +39,6 @@
 </div>
 
 <script>
-  var hbox = document.querySelector('.hbox');
-  test(function() {
-    assert_equals(hbox.parentNode.clientHeight, hbox.parentNode.scrollHeight);
-    assert_equals(hbox.clientHeight, hbox.scrollHeight);
-  }, 'hbox dimensions');
-
   var measure = document.getElementById('measure');
   var scrollbarSize = measure.offsetWidth - measure.clientWidth;
   test(function() {
diff --git a/third_party/blink/web_tests/css3/masking/clip-path-reference-local-url-with-base-expected.html b/third_party/blink/web_tests/css3/masking/clip-path-reference-local-url-with-base-expected.html
deleted file mode 100644
index f718ea6..0000000
--- a/third_party/blink/web_tests/css3/masking/clip-path-reference-local-url-with-base-expected.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<!DOCTYPE html>
-<div style="width: 100px; height: 100px; background-color: green"></div>
diff --git a/third_party/blink/web_tests/css3/masking/clip-path-reference-local-url-with-base.html b/third_party/blink/web_tests/css3/masking/clip-path-reference-local-url-with-base.html
deleted file mode 100644
index 3403359a..0000000
--- a/third_party/blink/web_tests/css3/masking/clip-path-reference-local-url-with-base.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<base href="http://www.example.com/">
-<style>
-#target {
-  width: 100px;
-  height: 100px;
-  border-right: 100px solid red;
-  background-color: green;
-  -webkit-clip-path: url(#clip);
-  clip-path: url(#clip);
-}
-</style>
-<div id="target"></div>
-<svg>
-  <clipPath id="clip">
-    <rect width="100" height="100"/>
-  </filter>
-</svg>
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
index 3122f6f5..4efca712 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
@@ -46059,6 +46059,18 @@
      {}
     ]
    ],
+   "css/css-flexbox/flex-minimum-height-flex-items-022.html": [
+    [
+     "css/css-flexbox/flex-minimum-height-flex-items-022.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-flexbox/flex-minimum-width-flex-items-001.xht": [
     [
      "css/css-flexbox/flex-minimum-width-flex-items-001.xht",
@@ -61625,6 +61637,18 @@
      {}
     ]
    ],
+   "css/css-masking/clip-path/reference-local-url-with-base-001.html": [
+    [
+     "css/css-masking/clip-path/reference-local-url-with-base-001.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/reference-local-url-with-base-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-masking/clip-rule/clip-rule-001.html": [
     [
      "css/css-masking/clip-rule/clip-rule-001.html",
@@ -108539,6 +108563,18 @@
      {}
     ]
    ],
+   "css/filter-effects/effect-reference-displacement-negative-scale-001.html": [
+    [
+     "css/filter-effects/effect-reference-displacement-negative-scale-001.html",
+     [
+      [
+       "/css/filter-effects/reference/effect-reference-displacement-negative-scale-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/filter-effects/effect-reference-feimage-001.html": [
     [
      "css/filter-effects/effect-reference-feimage-001.html",
@@ -108575,6 +108611,18 @@
      {}
     ]
    ],
+   "css/filter-effects/effect-reference-local-url-with-base-001.html": [
+    [
+     "css/filter-effects/effect-reference-local-url-with-base-001.html",
+     [
+      [
+       "/css/filter-effects/reference/effect-reference-local-url-with-base-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/filter-effects/effect-reference-merge-no-inputs.tentative.html": [
     [
      "css/filter-effects/effect-reference-merge-no-inputs.tentative.html",
@@ -108863,6 +108911,18 @@
      {}
     ]
    ],
+   "css/filter-effects/filter-region-transformed-child-001.html": [
+    [
+     "css/filter-effects/filter-region-transformed-child-001.html",
+     [
+      [
+       "/css/filter-effects/reference/filter-region-transformed-child-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/filter-effects/filter-saturate-001-test.html": [
     [
      "css/filter-effects/filter-saturate-001-test.html",
@@ -109151,12 +109211,24 @@
      {}
     ]
    ],
-   "css/filter-effects/svg-relative-urls-0001.html": [
+   "css/filter-effects/svg-relative-urls-001.html": [
     [
-     "css/filter-effects/svg-relative-urls-0001.html",
+     "css/filter-effects/svg-relative-urls-001.html",
      [
       [
-       "/css/filter-effects/reference/svg-relative-urls-0001-ref.html",
+       "/css/filter-effects/reference/svg-relative-urls-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/filter-effects/svg-relative-urls-002.html": [
+    [
+     "css/filter-effects/svg-relative-urls-002.html",
+     [
+      [
+       "/css/filter-effects/reference/svg-relative-urls-002-ref.html",
        "=="
       ]
      ],
@@ -150194,6 +150266,9 @@
    "css/css-masking/clip-path/reference/green-100x100.html": [
     []
    ],
+   "css/css-masking/clip-path/reference/reference-local-url-with-base-001-ref.html": [
+    []
+   ],
    "css/css-masking/clip-path/svg-clipPath.svg": [
     []
    ],
@@ -160085,6 +160160,9 @@
    "css/filter-effects/reference/effect-reference-delete-ref.html": [
     []
    ],
+   "css/filter-effects/reference/effect-reference-displacement-negative-scale-001-ref.html": [
+    []
+   ],
    "css/filter-effects/reference/effect-reference-feimage-001-ref.html": [
     []
    ],
@@ -160094,6 +160172,9 @@
    "css/filter-effects/reference/effect-reference-lighting-no-light.tentative-ref.html": [
     []
    ],
+   "css/filter-effects/reference/effect-reference-local-url-with-base-001-ref.html": [
+    []
+   ],
    "css/filter-effects/reference/effect-reference-merge-no-inputs.tentative-ref.html": [
     []
    ],
@@ -160115,6 +160196,9 @@
    "css/filter-effects/reference/filter-region-negative-positioned-child-001-ref.html": [
     []
    ],
+   "css/filter-effects/reference/filter-region-transformed-child-001-ref.html": [
+    []
+   ],
    "css/filter-effects/reference/filter-url-to-non-existent-filter-001-ref.html": [
     []
    ],
@@ -160151,7 +160235,10 @@
    "css/filter-effects/reference/svg-feoffset-ref.html": [
     []
    ],
-   "css/filter-effects/reference/svg-relative-urls-0001-ref.html": [
+   "css/filter-effects/reference/svg-relative-urls-001-ref.html": [
+    []
+   ],
+   "css/filter-effects/reference/svg-relative-urls-002-ref.html": [
     []
    ],
    "css/filter-effects/support/1x1-green.png": [
@@ -160205,6 +160292,9 @@
    "css/filter-effects/support/filter-external-002-filter.svg": [
     []
    ],
+   "css/filter-effects/support/filter-from-external-url.css": [
+    []
+   ],
    "css/filter-effects/support/filtersubregion00.png": [
     []
    ],
@@ -172244,6 +172334,9 @@
    "html/semantics/embedded-content/the-img-element/move-element-and-scroll-expected.txt": [
     []
    ],
+   "html/semantics/embedded-content/the-img-element/natural-size-orientation-expected.txt": [
+    []
+   ],
    "html/semantics/embedded-content/the-img-element/original-base-url-applied-2-expected.txt": [
     []
    ],
@@ -174155,6 +174248,9 @@
    "images/apng.png": [
     []
    ],
+   "images/arrow-oriented-upright.jpg": [
+    []
+   ],
    "images/background.png": [
     []
    ],
@@ -220546,6 +220642,12 @@
      {}
     ]
    ],
+   "css/css-flexbox/orthogonal-writing-modes-and-intrinsic-sizing.html": [
+    [
+     "css/css-flexbox/orthogonal-writing-modes-and-intrinsic-sizing.html",
+     {}
+    ]
+   ],
    "css/css-flexbox/overflow-auto-002.html": [
     [
      "css/css-flexbox/overflow-auto-002.html",
@@ -220786,6 +220888,12 @@
      {}
     ]
    ],
+   "css/css-flexbox/percentage-size.html": [
+    [
+     "css/css-flexbox/percentage-size.html",
+     {}
+    ]
+   ],
    "css/css-flexbox/position-absolute-001.html": [
     [
      "css/css-flexbox/position-absolute-001.html",
@@ -266939,6 +267047,12 @@
      {}
     ]
    ],
+   "html/semantics/embedded-content/the-img-element/natural-size-orientation.html": [
+    [
+     "html/semantics/embedded-content/the-img-element/natural-size-orientation.html",
+     {}
+    ]
+   ],
    "html/semantics/embedded-content/the-img-element/non-active-document.html": [
     [
      "html/semantics/embedded-content/the-img-element/non-active-document.html",
@@ -384261,6 +384375,10 @@
    "ed8d1d425180615aa0f5193f891c1c313438f79c",
    "reftest"
   ],
+  "css/css-flexbox/flex-minimum-height-flex-items-022.html": [
+   "943ac525fb570adcf62d7ff0edfc88cdd5727a85",
+   "reftest"
+  ],
   "css/css-flexbox/flex-minimum-size-001.html": [
    "c2eea80ca5b79818f6b7a9a36ab1ff64826b5218",
    "testharness"
@@ -386729,6 +386847,10 @@
    "ca9af99b939f77835933ccc76de5185b656f5977",
    "testharness"
   ],
+  "css/css-flexbox/orthogonal-writing-modes-and-intrinsic-sizing.html": [
+   "032cd47df5d6ae928bf766aad248ccf120bd033a",
+   "testharness"
+  ],
   "css/css-flexbox/overflow-auto-001.html": [
    "08c1bdbafabad98e7d823c47246f455cc0861fef",
    "reftest"
@@ -386965,6 +387087,10 @@
    "70f3953052a3a770c6cd15ee169607a00fc452b0",
    "reftest"
   ],
+  "css/css-flexbox/percentage-size.html": [
+   "44c298672f2e39716d21d36220b270f0387aac2f",
+   "testharness"
+  ],
   "css/css-flexbox/percentage-widths-001-ref.html": [
    "ca0c4d9b2a0c62244d9c1bd5bd3a9a7ed884b8f7",
    "support"
@@ -402957,6 +403083,10 @@
    "da3981feed6ed681ee4a7d88b7ecd167b9fd5c7b",
    "testharness"
   ],
+  "css/css-masking/clip-path/reference-local-url-with-base-001.html": [
+   "c65761bddfc095e0e85b4feaea4359516a8df5b1",
+   "reftest"
+  ],
   "css/css-masking/clip-path/reference/clip-path-circle-2-ref.html": [
    "7794d32b3f0e2415dbfda8ff12475b0e4f0b4117",
    "support"
@@ -403029,6 +403159,10 @@
    "f718ea6abfbab54333ba674ff0dcd320d8672bcd",
    "support"
   ],
+  "css/css-masking/clip-path/reference/reference-local-url-with-base-001-ref.html": [
+   "f718ea6abfbab54333ba674ff0dcd320d8672bcd",
+   "support"
+  ],
   "css/css-masking/clip-path/svg-clipPath.svg": [
    "d31a1df42e0ed94c43c2f9d0999d7dbde9e83130",
    "support"
@@ -441525,6 +441659,10 @@
    "314c9a7d123bd65b04483956513337116f7e0382",
    "reftest"
   ],
+  "css/filter-effects/effect-reference-displacement-negative-scale-001.html": [
+   "282ea100508948125f3a834af9dd37e0be210a10",
+   "reftest"
+  ],
   "css/filter-effects/effect-reference-feimage-001.html": [
    "3a8fb36db92fb408c7011f44724bc8457da7ff37",
    "reftest"
@@ -441537,6 +441675,10 @@
    "beefd47a544d5c82b4b1d468ce99938e6d9924d9",
    "reftest"
   ],
+  "css/filter-effects/effect-reference-local-url-with-base-001.html": [
+   "d3c81300e66fc8df37e89f690940df35426d8bf8",
+   "reftest"
+  ],
   "css/filter-effects/effect-reference-merge-no-inputs.tentative.html": [
    "4fb67db643dd5aebdbff53a0773035747c18836c",
    "reftest"
@@ -441677,6 +441819,10 @@
    "8f302ab52abe2f1008ca60d46fbc77d8ce35c22a",
    "reftest"
   ],
+  "css/filter-effects/filter-region-transformed-child-001.html": [
+   "cd097faf706a1d15f4ce5616f7e67fbf234dc50c",
+   "reftest"
+  ],
   "css/filter-effects/filter-saturate-001-ref.html": [
    "4f654f9c554d1e2ab98960ec291419d44375c1ae",
    "support"
@@ -441929,6 +442075,10 @@
    "918715265fa4c1b95ec46d04014ea82e73cbcd40",
    "support"
   ],
+  "css/filter-effects/reference/effect-reference-displacement-negative-scale-001-ref.html": [
+   "f718ea6abfbab54333ba674ff0dcd320d8672bcd",
+   "support"
+  ],
   "css/filter-effects/reference/effect-reference-feimage-001-ref.html": [
    "9b982b3cfb408badeb9b8b977000fc5e8935e5a6",
    "support"
@@ -441941,6 +442091,10 @@
    "e863a6703b2acebbdb10a5eef342cbbd1b6b5bc9",
    "support"
   ],
+  "css/filter-effects/reference/effect-reference-local-url-with-base-001-ref.html": [
+   "f718ea6abfbab54333ba674ff0dcd320d8672bcd",
+   "support"
+  ],
   "css/filter-effects/reference/effect-reference-merge-no-inputs.tentative-ref.html": [
    "5743e0c3de96ba4fbdf814bf5ec997e0c93e65da",
    "support"
@@ -441969,6 +442123,10 @@
    "c9da47b1b434303f0111e8d1f03d5518e8f573fc",
    "support"
   ],
+  "css/filter-effects/reference/filter-region-transformed-child-001-ref.html": [
+   "bcf649411930d5cc839dca2a5098e12c0c49fda8",
+   "support"
+  ],
   "css/filter-effects/reference/filter-url-to-non-existent-filter-001-ref.html": [
    "c1aeed8c441e6f72c6faa893d64ae30bdacbec06",
    "support"
@@ -442017,10 +442175,14 @@
    "5623b08ecd71b292e698ee249a79b59d0046300f",
    "support"
   ],
-  "css/filter-effects/reference/svg-relative-urls-0001-ref.html": [
+  "css/filter-effects/reference/svg-relative-urls-001-ref.html": [
    "82f301f5f6ff95f743f2488093fe63ac766a84bf",
    "support"
   ],
+  "css/filter-effects/reference/svg-relative-urls-002-ref.html": [
+   "344ee53e032e1bd588e971e16af2af5d0a6bcf41",
+   "support"
+  ],
   "css/filter-effects/root-element-with-opacity-filter-001.html": [
    "577e2bd4c032799a159c1c7542812e66081bfcea",
    "reftest"
@@ -442093,6 +442255,10 @@
    "0d5fc65c6fdfe2b4f507326fb379e468dbe85d46",
    "support"
   ],
+  "css/filter-effects/support/filter-from-external-url.css": [
+   "2940461016cad90f9218db1c7355434d9ca54e1c",
+   "support"
+  ],
   "css/filter-effects/support/filtersubregion00.png": [
    "b6c4bccb63cd850f473a59d9e3c0cf26c699db13",
    "support"
@@ -442233,8 +442399,12 @@
    "3d8118b387d938b588e8e88ad5ec87a5343e4f72",
    "reftest"
   ],
-  "css/filter-effects/svg-relative-urls-0001.html": [
-   "3edf7447b8f7fa0cf55f87a6ba051797bdd6f914",
+  "css/filter-effects/svg-relative-urls-001.html": [
+   "5dd382c6dfd2271449627e956f7723e810662cee",
+   "reftest"
+  ],
+  "css/filter-effects/svg-relative-urls-002.html": [
+   "278ea1645437f7afa797091b2562b9ad3430e458",
    "reftest"
   ],
   "css/filter-effects/svg-sourcegraphic-currentcolor-dynamic-001.html": [
@@ -476121,6 +476291,14 @@
    "3c95fae5bf354f68c20b9a2ce31f8c041dbee724",
    "testharness"
   ],
+  "html/semantics/embedded-content/the-img-element/natural-size-orientation-expected.txt": [
+   "3ef203a6ad5f984a0cf515334cc29121a9b37e01",
+   "support"
+  ],
+  "html/semantics/embedded-content/the-img-element/natural-size-orientation.html": [
+   "662dc0804fa32bff539467166fab134ce8ab22ff",
+   "testharness"
+  ],
   "html/semantics/embedded-content/the-img-element/non-active-document.html": [
    "6072138cb387d98e3bc8ae86971e424fe7061194",
    "testharness"
@@ -483645,6 +483823,10 @@
    "e96e6471e6a8df81c462c47be588ffbbcac5ecfb",
    "support"
   ],
+  "images/arrow-oriented-upright.jpg": [
+   "cebc99af14b6050a416031cf219f8cd7a72ee458",
+   "support"
+  ],
   "images/background.png": [
    "6db6c6b1b9d851c7a85b93ddc9e5ddf368ac0a7e",
    "support"
@@ -511754,7 +511936,7 @@
    "testharness"
   ],
   "shadow-dom/slots-imperative-slot-api.tentative-expected.txt": [
-   "39495dd6a5bcc6c0de06efdb57a4518be83c6d31",
+   "2d032ce4d6a52c2bd3f133a176ddfdae05c51a63",
    "support"
   ],
   "shadow-dom/slots-imperative-slot-api.tentative.html": [
@@ -516878,7 +517060,7 @@
    "support"
   ],
   "tools/ci/azure/safari-technology-preview.rb": [
-   "5f8d6806107d8abd7418ae022e59a1af9da1632f",
+   "d58664c5b6c67e32c63f1d122625a56211371511",
    "support"
   ],
   "tools/ci/azure/system_info.yml": [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/overflow-auto-008.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/overflow-auto-008.html
new file mode 100644
index 0000000..03f843a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/overflow-auto-008.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Flexbox: adding scrollbars with overflow: auto and flex-direction: column.</title>
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#overflow-properties">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-direction-property">
+<link rel="help" href="https://crbug.com/512229">
+<meta name="assert" content="This test checks that, when a block inside a flexbox with flex-direction: column adds horizontal scrollbars due to overflow, the parent flexbox is still able to render its contents without overflowing."/>
+
+<style>
+.vflex {
+  display: flex;
+  flex-direction: column;
+  max-width: 200px;
+  margin: 10px 0 10px 0;
+}
+
+.hbox {
+  overflow-x: auto;
+  white-space: nowrap;
+}
+
+.rect {
+  min-height: 100px;
+  min-width: 100px;
+  background-color: green;
+  display: inline-block;
+}
+</style>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<div class="vflex">
+  <div class="hbox">
+    <div class="rect"></div>
+    <div class="rect"></div>
+  </div>
+</div>
+
+<script>
+var hbox = document.querySelector('.hbox');
+test(function() {
+  assert_equals(hbox.parentNode.clientHeight, hbox.parentNode.scrollHeight);
+  assert_equals(hbox.clientHeight, hbox.scrollHeight);
+}, 'hbox dimensions');
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/reference-local-url-with-base-001.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/reference-local-url-with-base-001.html
new file mode 100644
index 0000000..c65761bd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/reference-local-url-with-base-001.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>CSS Masking: clip path with local URL using a base element</title>
+<link rel="author" title="Fredrik Söderquist" href="mailto:fs@opera.com">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-clip-path">
+<link rel="help" href="https://drafts.csswg.org/css-values/#local-urls">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=470608">
+<link rel="match" href="reference/reference-local-url-with-base-001-ref.html">
+<meta name="assert" content="Check that fragment-only URLs are always document-local references.">
+
+<base href="http://www.example.com/">
+<style>
+#target {
+  width: 100px;
+  height: 100px;
+  border-right: 100px solid red;
+  background-color: green;
+  clip-path: url(#clip);
+}
+</style>
+<div id="target"></div>
+<svg>
+  <clipPath id="clip">
+    <rect width="100" height="100"/>
+  </clipPath>
+</svg>
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-local-url-with-base-expected.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/reference/reference-local-url-with-base-001-ref.html
similarity index 100%
copy from third_party/blink/web_tests/css3/filters/effect-reference-local-url-with-base-expected.html
copy to third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/reference/reference-local-url-with-base-001-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-position-from-font-variable-ref.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-position-from-font-variable-ref.html
new file mode 100644
index 0000000..e621545
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-position-from-font-variable-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Text Decoration Test: text-underline-position respects variable font properties</title>
+<link rel="help" href="https://drafts.csswg.org/css-text-decor-4/#text-underline-position-property">
+<meta name="assert" content="text-underline-position from-font respects MVAR table of variable fonts for variable metrics">
+<link rel="author" title="Dominik Röttsches" href="mailto:drott@chromium.org">
+<style>
+@font-face {
+font-family: underline-close;
+src: url(../resources/UnderlineTest-Close.ttf);
+}
+
+@font-face {
+font-family: underline-far;
+src: url(../resources/UnderlineTest-Far.ttf);
+}
+
+.test {
+text-decoration: underline;
+text-underline-position: from-font;
+font-size: 64px;
+line-height: 1.8;
+}
+
+.close_underline {
+font-family: underline-close;
+}
+
+.far_underline {
+font-family: underline-far;
+}
+</style>
+</head>
+<body>
+    <p>Test passes if the underline on the first line is close to the baseline and far from the baseline on the
+    second line.</p>
+    <div class="test"><span class="close_underline">aagaa</span></div>
+    <div class="test"><span class="far_underline">aagaa</span></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/resources/UnderlineTest-Close.ttf b/third_party/blink/web_tests/external/wpt/css/css-text-decor/resources/UnderlineTest-Close.ttf
new file mode 100644
index 0000000..983ee05f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/resources/UnderlineTest-Close.ttf
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/resources/UnderlineTest-Far.ttf b/third_party/blink/web_tests/external/wpt/css/css-text-decor/resources/UnderlineTest-Far.ttf
new file mode 100644
index 0000000..cbca0981
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/resources/UnderlineTest-Far.ttf
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/resources/UnderlineTest-VF.ttf b/third_party/blink/web_tests/external/wpt/css/css-text-decor/resources/UnderlineTest-VF.ttf
new file mode 100644
index 0000000..2ac4de16
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/resources/UnderlineTest-VF.ttf
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-position-from-font-variable.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-position-from-font-variable.html
new file mode 100644
index 0000000..0459a2e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-position-from-font-variable.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Text Decoration Test: text-underline-position respects variable font properties</title>
+<link rel="help" href="https://drafts.csswg.org/css-text-decor-4/#text-underline-position-property">
+<meta name="assert" content="text-underline-position from-font respects MVAR table of variable fonts for variable metrics">
+<link rel="author" title="Dominik Röttsches" href="mailto:drott@chromium.org">
+<link rel="match" href="reference/text-underline-position-from-font-variable-ref.html">
+<style>
+@font-face {
+font-family: underline-variable;
+src: url(resources/UnderlineTest-VF.ttf);
+}
+
+.test {
+text-decoration: underline;
+text-underline-position: from-font;
+font-size: 64px;
+line-height: 1.8;
+}
+
+.close_underline {
+font-family: underline-variable, sans-serif;
+font-variation-settings: 'UNDO' 1;
+}
+
+.far_underline {
+font-family: underline-variable, sans-serif;
+font-variation-settings: 'UNDO' 1000;
+}
+</style>
+</head>
+<body>
+    <p>Test passes if the underline on the first line is close to the baseline and far from the baseline on the
+    second line.</p>
+    <div class="test"><span class="close_underline">aagaa</span></div>
+    <div class="test"><span class="far_underline">aagaa</span></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/clip-under-filter-001.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/clip-under-filter-001.html
new file mode 100644
index 0000000..7fbf0d6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/clip-under-filter-001.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>CSS Filters: Clips on descendants of filter elements are applied</title>
+<link rel="author" title="Xianzhu Wang" href="wangxianzhu@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#funcdef-filter-blur">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#overflow-properties">
+<link rel="help" href="https://crbug.com/809102">
+<link rel="match" href="reference/clip-under-filter-001-ref.html">
+<meta name="assert" content="Check that there is a blurred square, left half blue and right half green."/>
+
+<div style="filter: blur(10px)">
+  <div style="position: relative; width: 100px; height: 100px; background: blue; overflow: hidden">
+    <div style="position: absolute; width: 100px; height: 100px; right: -50px; background: green"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/clip-under-filter-002.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/clip-under-filter-002.html
new file mode 100644
index 0000000..8b6a66d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/clip-under-filter-002.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>CSS Filters: Clips on descendants of filter elements are applied</title>
+<link rel="author" title="Xianzhu Wang" href="wangxianzhu@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#funcdef-filter-blur">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#overflow-properties">
+<link rel="help" href="https://crbug.com/809102">
+<link rel="match" href="reference/clip-under-filter-002-ref.html">
+<meta name="assert" content="Check that there is a blurred square, left half blue and right half green"/>
+
+<div style="filter: blur(10px)">
+  <div style="position: relative; width: 100px; height: 100px; background: blue; overflow: hidden">
+    <div style="position: absolute; width: 100px; height: 100px; right: -50px; background: green; overflow: hidden"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/crashtests/multiple-references-id-crash-001.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/crashtests/multiple-references-id-crash-001.html
new file mode 100644
index 0000000..9ee04e1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/crashtests/multiple-references-id-crash-001.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Filters: multiple references to non-existent filter</title>
+<link rel="author" title="Fredrik Söderquist" href="mailto:fs@opera.com">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#FilterProperty">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=439970">
+<meta name="assert" content="Check that applying the same filter pointing to a non-existing target from multiple elements does not crash when removed."/>
+
+<style>
+.filter {
+  filter: url(#foo);
+}
+</style>
+<body onload="runTest()">
+<div class="filter"></div>
+<div class="filter"></div>
+<button form="bar"></button>
+
+<script>
+function runTest() {
+  document.body.innerHTML = "PASS if no crash";
+}
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-displacement-negative-scale-001.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-displacement-negative-scale-001.html
new file mode 100644
index 0000000..282ea10
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-displacement-negative-scale-001.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>SVG Filters: feDisplacementMap filter with negative scale</title>
+<link rel="author" title="Fredrik Söderquist" href="mailto:fs@opera.com">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#FilterProperty">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#feDisplacementMapElement">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=641854">
+<link rel="match" href="reference/effect-reference-displacement-negative-scale-001-ref.html">
+<meta name="assert" content="feDisplacementMap with a negative scale applies the displacement in the opposite direction.">
+
+<div style="position: relative">
+  <div style="position: absolute; top: 20px; left: 20px; width: 100px; height: 100px; background: green; filter: url(#displacementFilter);"></div>
+</div>
+<svg width="0" height="0">
+  <filter id="displacementFilter" x="-0.2" y="-0.2" color-interpolation-filters="sRGB">
+    <feFlood flood-color="black"/>
+    <feDisplacementMap in="SourceGraphic" scale="-40" xChannelSelector="R" yChannelSelector="R"/>
+  </filter>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-feimage-003.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-feimage-003.html
new file mode 100644
index 0000000..887f5ff
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-feimage-003.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>CSS Filters: feImage and CSS reference filters.</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#feImageElement">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#FilterProperty">
+<link rel="match" href="reference/effect-reference-feimage-003-ref.html">
+<meta name="assert" content="This test ensures that CSS reference filters support a dynamically attached feImage."/>
+<body>
+
+<style>
+  #filtered {
+    width: 160px;
+    height: 90px;
+    filter: url(#imagereplace);
+  }
+</style>
+
+<script type="text/html" id="source">
+  <div id="filtered"></div>
+  <svg width="0" height="0">
+    <filter id="imagereplace" x="0%" y="0" width="100%" height="100%">
+       <feimage xlink:href="support/color-palette.png"/>
+    </filter>
+  </svg>
+</script>
+
+<script>
+  document.body.offsetLeft;
+  var div = document.createElement('div');
+  div.innerHTML = document.getElementById('source').textContent;
+  document.body.appendChild(div);
+</script>
+
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-local-url-with-base-001.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-local-url-with-base-001.html
new file mode 100644
index 0000000..d3c8130
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-local-url-with-base-001.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>SVG Filters: local URL with a base element</title>
+<link rel="author" title="Fredrik Söderquist" href="mailto:fs@opera.com">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#FilterProperty">
+<link rel="help" href="https://drafts.csswg.org/css-values/#local-urls">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=470608">
+<link rel="match" href="reference/effect-reference-local-url-with-base-001-ref.html">
+<meta name="assert" content="Check that fragment-only URLs are always document-local references.">
+
+<base href="http://www.example.com/">
+<style>
+#target {
+  width: 100px;
+  height: 100px;
+  background-color: red;
+  filter: url(#filter);
+}
+</style>
+<div id="target"></div>
+<svg>
+  <filter id="filter" x="0" y="0" width="1" height="1" color-interpolation-filters="sRGB">
+    <feFlood flood-color="green"/>
+  </filter>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-on-transparent-element.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-on-transparent-element.html
new file mode 100644
index 0000000..e88a656
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-on-transparent-element.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Filters: SVG filter on transparent element</title>
+<link rel="author" title="Stephen White" href="mailto:senorblanco@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#FilterProperty">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=510541">
+<link rel="match" href="reference/effect-reference-on-transparent-element-ref.html">
+<meta name="assert" content="Check that applying a SVG filter to a transparent element works as expected. You should see a green lime colored square."/>
+
+<style>
+.box {
+  width: 100px;
+  height: 100px;
+  filter: url(#flood_green);
+}
+</style>
+
+<body>
+<div class="box"></div>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0">
+  <defs>
+    <filter id="flood_green" x="0%" y="0%" width="100%" height="100%" color-interpolation-filters="sRGB">
+      <feColorMatrix type="matrix" values="0 0 0 0 0
+                                           0 0 0 0 1
+                                           0 0 0 0 0
+                                           0 0 0 0 1"/>
+    </filter>
+  </defs>
+</svg>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-source-alpha-001.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-source-alpha-001.html
new file mode 100644
index 0000000..3bb7601
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-source-alpha-001.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Filters: feMergeNode with SourceAlpha</title>
+<link rel="author" title="Fredrik Söderquist" href="mailto:fs@opera.com">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#feMergeElement">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#attr-valuedef-in-sourcealpha">
+<link rel="help" href="https://crbug.com/109224">
+<link rel="match" href="reference/effect-reference-source-alpha-001-ref.html">
+<meta name="assert" content="Check that SourceAlpha is correctly derived from SourceGraphic which is the output of the opacity(0) filter function which essentially sets all pixels to transparent."/>
+
+<style>
+div {
+  position: absolute;
+  width: 100px;
+  height: 100px;
+}
+#test {
+  background-color: red;
+  filter: opacity(0) url(#f);
+}
+</style>
+<div style="background-color: green"></div>
+<div id="test">
+<svg height="0">
+  <filter id="f">
+    <feMerge><feMergeNode in="SourceAlpha"></feMergeNode></feMerge>
+  </filter>
+</svg>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-source-alpha-002.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-source-alpha-002.html
new file mode 100644
index 0000000..3c570c5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/effect-reference-source-alpha-002.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Filters: feColorMatrix with SourceAlpha</title>
+<link rel="author" title="Stephen White" href="mailto:senorblanco@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#feColorMatrixElement">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#attr-valuedef-in-sourcealpha">
+<link rel="help" href="https://crbug.com/331362">
+<link rel="match" href="reference/effect-reference-source-alpha-002-ref.html">
+<meta name="assert" content="Check that the result of an identity feColorMatrix with SourceAlpha from an element with will-change: transform as input is opaque black"/>
+
+<style>
+.rect {
+    position: absolute;
+    background-color: red;
+    width: 100px;
+    height: 100px;
+    filter: url(#alpha);
+    will-change: transform;
+}
+</style>
+<div class="rect"></div>
+<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" version="1.1">
+  <defs>
+    <filter id="alpha" color-interpolation-filters="sRGB">
+      <feColorMatrix in="SourceAlpha"/>
+    </filter>
+  </defs>
+</svg>
diff --git a/third_party/blink/web_tests/paint/filters/absolute-under-clip-under-filter-expected.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/clip-under-filter-001-ref.html
similarity index 74%
rename from third_party/blink/web_tests/paint/filters/absolute-under-clip-under-filter-expected.html
rename to third_party/blink/web_tests/external/wpt/css/filter-effects/reference/clip-under-filter-001-ref.html
index ae3e008..60c2c33 100644
--- a/third_party/blink/web_tests/paint/filters/absolute-under-clip-under-filter-expected.html
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/clip-under-filter-001-ref.html
@@ -1,5 +1,4 @@
 <!DOCTYPE html>
-Passes if there is a blurred rectangle, left half blue and right half green.
 <div style="filter: blur(10px)">
   <div style="width: 100px; height: 100px; background: blue">
     <div style="position: relative; left: 50px; width: 50px; height: 100px; background: green"></div>
diff --git a/third_party/blink/web_tests/paint/filters/absolute-under-clip-under-filter-expected.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/clip-under-filter-002-ref.html
similarity index 74%
copy from third_party/blink/web_tests/paint/filters/absolute-under-clip-under-filter-expected.html
copy to third_party/blink/web_tests/external/wpt/css/filter-effects/reference/clip-under-filter-002-ref.html
index ae3e008..60c2c33 100644
--- a/third_party/blink/web_tests/paint/filters/absolute-under-clip-under-filter-expected.html
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/clip-under-filter-002-ref.html
@@ -1,5 +1,4 @@
 <!DOCTYPE html>
-Passes if there is a blurred rectangle, left half blue and right half green.
 <div style="filter: blur(10px)">
   <div style="width: 100px; height: 100px; background: blue">
     <div style="position: relative; left: 50px; width: 50px; height: 100px; background: green"></div>
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-displacement-negative-scale-expected.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-displacement-negative-scale-001-ref.html
similarity index 100%
rename from third_party/blink/web_tests/css3/filters/effect-reference-displacement-negative-scale-expected.html
rename to third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-displacement-negative-scale-001-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-feimage-003-ref.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-feimage-003-ref.html
new file mode 100644
index 0000000..9b982b3c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-feimage-003-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<img src="../support/color-palette.png">
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-local-url-with-base-expected.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-local-url-with-base-001-ref.html
similarity index 100%
rename from third_party/blink/web_tests/css3/filters/effect-reference-local-url-with-base-expected.html
rename to third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-local-url-with-base-001-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-on-transparent-element-ref.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-on-transparent-element-ref.html
new file mode 100644
index 0000000..6e27519
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-on-transparent-element-ref.html
@@ -0,0 +1,8 @@
+<style>
+.box {
+  width: 100px;
+  height: 100px;
+  background-color: rgb(0, 255, 0);
+}
+</style>
+<div class="box"></div>
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-local-url-with-base-expected.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-source-alpha-001-ref.html
similarity index 100%
copy from third_party/blink/web_tests/css3/filters/effect-reference-local-url-with-base-expected.html
copy to third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-source-alpha-001-ref.html
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-source-alpha-hw-expected.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-source-alpha-002-ref.html
similarity index 72%
rename from third_party/blink/web_tests/css3/filters/effect-reference-source-alpha-hw-expected.html
rename to third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-source-alpha-002-ref.html
index 893f3088..eb106f3 100644
--- a/third_party/blink/web_tests/css3/filters/effect-reference-source-alpha-hw-expected.html
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/effect-reference-source-alpha-002-ref.html
@@ -1,9 +1,10 @@
+<!DOCTYPE html>
 <style>
 .rect {
     background-color: black;
     width: 100px;
     height: 100px;
-    transform: translateZ(0);
+    will-change: transform;
 }
 </style>
 <div class="rect"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/svg-relative-urls-0001-ref.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/svg-relative-urls-001-ref.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/filter-effects/reference/svg-relative-urls-0001-ref.html
rename to third_party/blink/web_tests/external/wpt/css/filter-effects/reference/svg-relative-urls-001-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/svg-relative-urls-002-ref.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/svg-relative-urls-002-ref.html
new file mode 100644
index 0000000..344ee53e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/svg-relative-urls-002-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<style>
+img {
+    margin: 10px;
+}
+</style>
+<img style="filter: url(../support/hueRotate.svg#MyFilter)" src="../support/color-palette.png">
diff --git a/third_party/blink/web_tests/css3/filters/resources/filters.css b/third_party/blink/web_tests/external/wpt/css/filter-effects/support/filter-from-external-url.css
similarity index 72%
rename from third_party/blink/web_tests/css3/filters/resources/filters.css
rename to third_party/blink/web_tests/external/wpt/css/filter-effects/support/filter-from-external-url.css
index ff0a2aa7..2940461 100644
--- a/third_party/blink/web_tests/css3/filters/resources/filters.css
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/support/filter-from-external-url.css
@@ -1,3 +1,3 @@
-.huerotate180 {
+.hue-rotate {
     filter: url('hueRotate.svg#MyFilter');
-}
\ No newline at end of file
+}
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-relative-urls-0001.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-relative-urls-001.html
similarity index 92%
rename from third_party/blink/web_tests/external/wpt/css/filter-effects/svg-relative-urls-0001.html
rename to third_party/blink/web_tests/external/wpt/css/filter-effects/svg-relative-urls-001.html
index 3edf7447..5dd382c 100644
--- a/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-relative-urls-0001.html
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-relative-urls-001.html
@@ -4,7 +4,7 @@
 <link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#FilterProperty">
 <link rel="help" href="https://drafts.csswg.org/css-values-4/#relative-urls">
 <link rel="help" href="https://crbug.com/405315"/>
-<link rel="match" href="reference/svg-relative-urls-0001-ref.html">
+<link rel="match" href="reference/svg-relative-urls-001-ref.html">
 <meta name="assert" content="This test checks that relative SVG filter references in inline styles are correct after an element moves documents.">
 
 <body onload="runTest()">
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-relative-urls-002.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-relative-urls-002.html
new file mode 100644
index 0000000..278ea164
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-relative-urls-002.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>SVG Filters: relative URLs from external stylesheets</title>
+<link rel="author" title="Fredrik Söderquist" href="mailto:fs@opera.com">
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#relative-urls">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#FilterProperty">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=405315">
+<link rel="match" href="reference/svg-relative-urls-002-ref.html">
+<meta name="assert" content="Check that SVG filters can be referenced from external stylesheets using a relative URL">
+
+<link rel="stylesheet" href="support/filter-from-external-url.css"
+      onload="document.body && document.body.offsetTop">
+<style>
+img {
+    margin: 10px;
+}
+</style>
+<img class="hue-rotate" src="support/color-palette.png">
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/natural-size-orientation-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/natural-size-orientation-expected.txt
new file mode 100644
index 0000000..3ef203a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/natural-size-orientation-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+PASS naturalWidth and naturalHeight return correct values for an image without orientation metadata
+PASS naturalWidth and naturalHeight return re-oriented values for an image with orientation metadata
+FAIL naturalWidth and naturalHeight return re-oriented values for an image with orientation metadata even with image-orientation:none assert_equals: expected 144 but got 240
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/natural-size-orientation.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/natural-size-orientation.html
new file mode 100644
index 0000000..662dc08
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/natural-size-orientation.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>naturalWidth and naturalHeight on HTMLImageElement reflect orientation metadata</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+.ignore-orientation { image-orientation: none; }
+</style>
+<body>
+<script>
+async_test(function(t) {
+  let img = document.createElement("img");
+  img.src = "/images/green-100x50.png";
+  img.onload = t.step_func_done(function() {
+    assert_equals(img.naturalWidth, 100);
+    assert_equals(img.naturalHeight, 50);
+    img.remove();
+  });
+  document.body.append(img);
+}, "naturalWidth and naturalHeight return correct values for an image without orientation metadata");
+
+async_test(function(t) {
+  let img = document.createElement("img");
+  img.src = "/images/arrow-oriented-upright.jpg";
+  img.onload = t.step_func_done(function() {
+    assert_equals(img.naturalWidth, 144);
+    assert_equals(img.naturalHeight, 240);
+    img.remove();
+  });
+  document.body.append(img);
+}, "naturalWidth and naturalHeight return re-oriented values for an image with orientation metadata");
+
+async_test(function(t) {
+  let img = document.createElement("img");
+  img.src = "/images/arrow-oriented-upright.jpg";
+  img.className = "ignore-orientation";
+  img.onload = t.step_func_done(function() {
+    assert_equals(img.naturalWidth, 144);
+    assert_equals(img.naturalHeight, 240);
+    img.remove();
+  });
+  document.body.append(img);
+}, "naturalWidth and naturalHeight return re-oriented values for an image with orientation metadata even with image-orientation:none");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/images/arrow-oriented-upright.jpg b/third_party/blink/web_tests/external/wpt/images/arrow-oriented-upright.jpg
new file mode 100644
index 0000000..cebc99a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/images/arrow-oriented-upright.jpg
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/sandboxed-iframe-fetch-event.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/sandboxed-iframe-fetch-event.https.html
index e08b7164..ba34e790 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/sandboxed-iframe-fetch-event.https.html
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/sandboxed-iframe-fetch-event.https.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 <title>ServiceWorker FetchEvent for sandboxed iframe.</title>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/test-helpers.sub.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/shadow-dom/slots-imperative-slot-api.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/shadow-dom/slots-imperative-slot-api.tentative-expected.txt
index 39495dd..2d032ce 100644
--- a/third_party/blink/web_tests/external/wpt/shadow-dom/slots-imperative-slot-api.tentative-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/shadow-dom/slots-imperative-slot-api.tentative-expected.txt
@@ -6,7 +6,7 @@
 PASS Order of slotables is preserved in manual slot assignment.
 PASS Previously assigned slotable is moved to new slot when it's reassigned.
 PASS Order and assignment of nodes are preserved during multiple assignment in a row.
-FAIL Assigning invalid nodes causes exception and slot returns to its previous state. assert_array_equals: lengths differ, expected array [] length 0, got [Element node <div id="c1"></div>, Element node <div id="c2"></div>] length 2
+PASS Assigning invalid nodes causes exception and slot returns to its previous state.
 PASS Moving a slot to a new host, the slot loses its previously assigned slotables.
 PASS Moving a slot's tree order position within a shadow host has no impact on its assigned slotables.
 PASS Appending slotable to different host, it loses slot assignment. It can be re-assigned within a new host.
diff --git a/third_party/blink/web_tests/external/wpt/tools/ci/azure/safari-technology-preview.rb b/third_party/blink/web_tests/external/wpt/tools/ci/azure/safari-technology-preview.rb
index 5f8d680..d58664c 100644
--- a/third_party/blink/web_tests/external/wpt/tools/ci/azure/safari-technology-preview.rb
+++ b/third_party/blink/web_tests/external/wpt/tools/ci/azure/safari-technology-preview.rb
@@ -1,10 +1,10 @@
 cask 'safari-technology-preview' do
   if MacOS.version <= :mojave
-    version '103,061-90754-20200325-37467264-9c34-454c-be4a-1cb87e93c62c'
-    sha256 'f2175a2ca69152b6c1067d47b56d464b0ba0c71323a76137b488a8088a25f44c'
+    version '104,061-96689-20200407-7f675598-e07d-46db-8b24-b10c33a006ba'
+    sha256 'bc1b0a79f99022aae802bbb1e83a6a13613a339115063489fc240567b1b5d81d'
   else
-    version '103,061-90752-20200325-cc5192c4-619a-45b4-83b5-70a1bed8c912'
-    sha256 '846f2c9e7ebcc293f01ea36c8e1184e2d1bfb985eb3a590fc7c730c40e10a4b6'
+    version '104,061-96685-20200407-907fca12-9ba9-4bfa-8b8c-d5bf101d534b'
+    sha256 '3dcfde56386f38984c9ede1d67714c43699c5cafece9608ee2730484bffdcf9c'
   end
 
   url "https://secure-appldnld.apple.com/STP/#{version.after_comma}/SafariTechnologyPreview.dmg"
diff --git a/third_party/blink/web_tests/external/wpt/wasm/idlharness.any-expected.txt b/third_party/blink/web_tests/external/wpt/wasm/idlharness.any-expected.txt
deleted file mode 100644
index b55d60b..0000000
--- a/third_party/blink/web_tests/external/wpt/wasm/idlharness.any-expected.txt
+++ /dev/null
@@ -1,85 +0,0 @@
-This is a testharness.js-based test.
-Found 81 tests; 7 PASS, 74 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS wasm-js-api interfaces.
-FAIL WebAssembly namespace: operation validate(BufferSource) assert_true: property should be enumerable expected true got false
-FAIL WebAssembly namespace: operation compile(BufferSource) assert_true: property should be enumerable expected true got false
-FAIL WebAssembly namespace: operation instantiate(BufferSource, object) assert_true: property should be enumerable expected true got false
-FAIL WebAssembly namespace: operation instantiate(Module, object) assert_true: property should be enumerable expected true got false
-FAIL Module interface: existence and properties of interface object assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface object length assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface object name assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface: existence and properties of interface prototype object assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface: operation exports(Module) assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface: operation imports(Module) assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface: operation customSections(Module, USVString) assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module must be primary interface of mod assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Stringification of mod assert_equals: class string of mod expected "[object Module]" but got "[object WebAssembly.Module]"
-PASS Module interface: mod must inherit property "exports(Module)" with the proper type
-PASS Module interface: calling exports(Module) on mod with too few arguments must throw TypeError
-PASS Module interface: mod must inherit property "imports(Module)" with the proper type
-PASS Module interface: calling imports(Module) on mod with too few arguments must throw TypeError
-PASS Module interface: mod must inherit property "customSections(Module, USVString)" with the proper type
-PASS Module interface: calling customSections(Module, USVString) on mod with too few arguments must throw TypeError
-FAIL Instance interface: existence and properties of interface object assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance interface object length assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance interface object name assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance interface: existence and properties of interface prototype object assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance interface: attribute exports assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance must be primary interface of instance assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: instance is not defined"
-FAIL Stringification of instance assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: instance is not defined"
-FAIL Instance interface: instance must inherit property "exports" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: instance is not defined"
-FAIL Memory interface: existence and properties of interface object assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface object length assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface object name assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface: existence and properties of interface prototype object assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface: operation grow(unsigned long) assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface: attribute buffer assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory must be primary interface of memory assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: memory is not defined"
-FAIL Stringification of memory assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: memory is not defined"
-FAIL Memory interface: memory must inherit property "grow(unsigned long)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: memory is not defined"
-FAIL Memory interface: calling grow(unsigned long) on memory with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: memory is not defined"
-FAIL Memory interface: memory must inherit property "buffer" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: memory is not defined"
-FAIL Table interface: existence and properties of interface object assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface object length assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface object name assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: existence and properties of interface prototype object assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: operation grow(unsigned long) assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: operation get(unsigned long) assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: operation set(unsigned long, Function) assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: attribute length assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Global interface: existence and properties of interface object assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface object length assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface object name assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface: existence and properties of interface prototype object assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface: operation valueOf() assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface: attribute value assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL CompileError interface: existence and properties of interface object assert_own_property: self does not have own property "CompileError" expected property "CompileError" missing
-FAIL CompileError interface object length assert_own_property: self does not have own property "CompileError" expected property "CompileError" missing
-FAIL CompileError interface object name assert_own_property: self does not have own property "CompileError" expected property "CompileError" missing
-FAIL CompileError interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CompileError" expected property "CompileError" missing
-FAIL CompileError interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CompileError" expected property "CompileError" missing
-FAIL CompileError interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CompileError" expected property "CompileError" missing
-FAIL LinkError interface: existence and properties of interface object assert_own_property: self does not have own property "LinkError" expected property "LinkError" missing
-FAIL LinkError interface object length assert_own_property: self does not have own property "LinkError" expected property "LinkError" missing
-FAIL LinkError interface object name assert_own_property: self does not have own property "LinkError" expected property "LinkError" missing
-FAIL LinkError interface: existence and properties of interface prototype object assert_own_property: self does not have own property "LinkError" expected property "LinkError" missing
-FAIL LinkError interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "LinkError" expected property "LinkError" missing
-FAIL LinkError interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "LinkError" expected property "LinkError" missing
-FAIL RuntimeError interface: existence and properties of interface object assert_own_property: self does not have own property "RuntimeError" expected property "RuntimeError" missing
-FAIL RuntimeError interface object length assert_own_property: self does not have own property "RuntimeError" expected property "RuntimeError" missing
-FAIL RuntimeError interface object name assert_own_property: self does not have own property "RuntimeError" expected property "RuntimeError" missing
-FAIL RuntimeError interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RuntimeError" expected property "RuntimeError" missing
-FAIL RuntimeError interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RuntimeError" expected property "RuntimeError" missing
-FAIL RuntimeError interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RuntimeError" expected property "RuntimeError" missing
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/wasm/idlharness.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/wasm/idlharness.any.worker-expected.txt
deleted file mode 100644
index 4aabd11..0000000
--- a/third_party/blink/web_tests/external/wpt/wasm/idlharness.any.worker-expected.txt
+++ /dev/null
@@ -1,70 +0,0 @@
-This is a testharness.js-based test.
-Found 66 tests; 10 PASS, 56 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS wasm-js-api interfaces.
-FAIL WebAssembly namespace: operation validate(BufferSource) assert_true: property should be enumerable expected true got false
-FAIL WebAssembly namespace: operation compile(BufferSource) assert_true: property should be enumerable expected true got false
-FAIL WebAssembly namespace: operation instantiate(BufferSource, object) assert_true: property should be enumerable expected true got false
-FAIL WebAssembly namespace: operation instantiate(Module, object) assert_true: property should be enumerable expected true got false
-FAIL Module interface: existence and properties of interface object assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface object length assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface object name assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface: existence and properties of interface prototype object assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface: operation exports(Module) assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface: operation imports(Module) assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module interface: operation customSections(Module, USVString) assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Module must be primary interface of mod assert_own_property: self does not have own property "Module" expected property "Module" missing
-FAIL Stringification of mod assert_equals: class string of mod expected "[object Module]" but got "[object WebAssembly.Module]"
-PASS Module interface: mod must inherit property "exports(Module)" with the proper type
-PASS Module interface: calling exports(Module) on mod with too few arguments must throw TypeError
-PASS Module interface: mod must inherit property "imports(Module)" with the proper type
-PASS Module interface: calling imports(Module) on mod with too few arguments must throw TypeError
-PASS Module interface: mod must inherit property "customSections(Module, USVString)" with the proper type
-PASS Module interface: calling customSections(Module, USVString) on mod with too few arguments must throw TypeError
-FAIL Instance interface: existence and properties of interface object assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance interface object length assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance interface object name assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance interface: existence and properties of interface prototype object assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance interface: attribute exports assert_own_property: self does not have own property "Instance" expected property "Instance" missing
-FAIL Instance must be primary interface of instance assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: instance is not defined"
-FAIL Stringification of instance assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: instance is not defined"
-FAIL Instance interface: instance must inherit property "exports" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: instance is not defined"
-FAIL Memory interface: existence and properties of interface object assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface object length assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface object name assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface: existence and properties of interface prototype object assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface: operation grow(unsigned long) assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory interface: attribute buffer assert_own_property: self does not have own property "Memory" expected property "Memory" missing
-FAIL Memory must be primary interface of memory assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: memory is not defined"
-FAIL Stringification of memory assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: memory is not defined"
-FAIL Memory interface: memory must inherit property "grow(unsigned long)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: memory is not defined"
-FAIL Memory interface: calling grow(unsigned long) on memory with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: memory is not defined"
-FAIL Memory interface: memory must inherit property "buffer" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: memory is not defined"
-FAIL Table interface: existence and properties of interface object assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface object length assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface object name assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: existence and properties of interface prototype object assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: operation grow(unsigned long) assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: operation get(unsigned long) assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: operation set(unsigned long, Function) assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Table interface: attribute length assert_own_property: self does not have own property "Table" expected property "Table" missing
-FAIL Global interface: existence and properties of interface object assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface object length assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface object name assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface: existence and properties of interface prototype object assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface: operation valueOf() assert_own_property: self does not have own property "Global" expected property "Global" missing
-FAIL Global interface: attribute value assert_own_property: self does not have own property "Global" expected property "Global" missing
-PASS CompileError interface: existence and properties of interface object
-PASS LinkError interface: existence and properties of interface object
-PASS RuntimeError interface: existence and properties of interface object
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/wasm/jsapi/global/value-set.any-expected.txt b/third_party/blink/web_tests/external/wpt/wasm/jsapi/global/value-set.any-expected.txt
deleted file mode 100644
index 6410e1c..0000000
--- a/third_party/blink/web_tests/external/wpt/wasm/jsapi/global/value-set.any-expected.txt
+++ /dev/null
@@ -1,36 +0,0 @@
-This is a testharness.js-based test.
-PASS Branding
-PASS Immutable i32 (missing)
-PASS Immutable i32 (undefined)
-PASS Immutable i32 (null)
-PASS Immutable i32 (false)
-PASS Immutable i32 (empty string)
-PASS Immutable i32 (zero)
-PASS Mutable i32 (true)
-PASS Mutable i32 (one)
-PASS Mutable i32 (string)
-PASS Mutable i32 (true on prototype)
-PASS Immutable f32 (missing)
-PASS Immutable f32 (undefined)
-PASS Immutable f32 (null)
-PASS Immutable f32 (false)
-PASS Immutable f32 (empty string)
-PASS Immutable f32 (zero)
-PASS Mutable f32 (true)
-PASS Mutable f32 (one)
-PASS Mutable f32 (string)
-PASS Mutable f32 (true on prototype)
-PASS Immutable f64 (missing)
-PASS Immutable f64 (undefined)
-PASS Immutable f64 (null)
-PASS Immutable f64 (false)
-PASS Immutable f64 (empty string)
-PASS Immutable f64 (zero)
-PASS Mutable f64 (true)
-PASS Mutable f64 (one)
-PASS Mutable f64 (string)
-PASS Mutable f64 (true on prototype)
-FAIL i64 with default WebAssembly.Global(): Descriptor property 'value' must be 'i32', 'f32', or 'f64'
-FAIL Calling setter without argument assert_throws: function "() => setter.call(global)" did not throw
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/wasm/jsapi/global/value-set.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/wasm/jsapi/global/value-set.any.worker-expected.txt
deleted file mode 100644
index 6410e1c..0000000
--- a/third_party/blink/web_tests/external/wpt/wasm/jsapi/global/value-set.any.worker-expected.txt
+++ /dev/null
@@ -1,36 +0,0 @@
-This is a testharness.js-based test.
-PASS Branding
-PASS Immutable i32 (missing)
-PASS Immutable i32 (undefined)
-PASS Immutable i32 (null)
-PASS Immutable i32 (false)
-PASS Immutable i32 (empty string)
-PASS Immutable i32 (zero)
-PASS Mutable i32 (true)
-PASS Mutable i32 (one)
-PASS Mutable i32 (string)
-PASS Mutable i32 (true on prototype)
-PASS Immutable f32 (missing)
-PASS Immutable f32 (undefined)
-PASS Immutable f32 (null)
-PASS Immutable f32 (false)
-PASS Immutable f32 (empty string)
-PASS Immutable f32 (zero)
-PASS Mutable f32 (true)
-PASS Mutable f32 (one)
-PASS Mutable f32 (string)
-PASS Mutable f32 (true on prototype)
-PASS Immutable f64 (missing)
-PASS Immutable f64 (undefined)
-PASS Immutable f64 (null)
-PASS Immutable f64 (false)
-PASS Immutable f64 (empty string)
-PASS Immutable f64 (zero)
-PASS Mutable f64 (true)
-PASS Mutable f64 (one)
-PASS Mutable f64 (string)
-PASS Mutable f64 (true on prototype)
-FAIL i64 with default WebAssembly.Global(): Descriptor property 'value' must be 'i32', 'f32', or 'f64'
-FAIL Calling setter without argument assert_throws: function "() => setter.call(global)" did not throw
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/wasm/jsapi/memory/constructor.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/wasm/jsapi/memory/constructor.any.worker-expected.txt
deleted file mode 100644
index e69de29..0000000
--- a/third_party/blink/web_tests/external/wpt/wasm/jsapi/memory/constructor.any.worker-expected.txt
+++ /dev/null
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/module/nested-worker-success-sharedworker-expected.txt b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/nested-worker-success-sharedworker-expected.txt
deleted file mode 100644
index 7b70ea2..0000000
--- a/third_party/blink/web_tests/external/wpt/wasm/serialization/module/nested-worker-success-sharedworker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL postMessaging to a dedicated sub-worker allows them to see each others' modifications Worker is not defined
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/http/tests/devtools/console/console-format-es6-expected.txt b/third_party/blink/web_tests/http/tests/devtools/console/console-format-es6-expected.txt
index b7c5528f..0a9a397f 100644
--- a/third_party/blink/web_tests/http/tests/devtools/console/console-format-es6-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/console/console-format-es6-expected.txt
@@ -4,10 +4,10 @@
 console-format-es6.js:16 [Promise]
 globals[0]
 Promise {<rejected>: -0}
-console-format-es6.js:15 Promise {<resolved>: 1}
+console-format-es6.js:15 Promise {<fulfilled>: 1}
 console-format-es6.js:16 [Promise]
 globals[1]
-Promise {<resolved>: 1}
+Promise {<fulfilled>: 1}
 console-format-es6.js:15 Promise {<pending>}
 console-format-es6.js:16 [Promise]
 globals[2]
@@ -78,18 +78,18 @@
     __proto__: Promise
     [[PromiseStatus]]: "rejected"
     [[PromiseValue]]: -0
-console-format-es6.js:15 Promise {<resolved>: 1}
+console-format-es6.js:15 Promise {<fulfilled>: 1}
     __proto__: Promise
-    [[PromiseStatus]]: "resolved"
+    [[PromiseStatus]]: "fulfilled"
     [[PromiseValue]]: 1
 console-format-es6.js:16 [Promise]
-    0: Promise {<resolved>: 1}
+    0: Promise {<fulfilled>: 1}
     length: 1
     __proto__: Array(0)
 globals[1]
-Promise {<resolved>: 1}
+Promise {<fulfilled>: 1}
     __proto__: Promise
-    [[PromiseStatus]]: "resolved"
+    [[PromiseStatus]]: "fulfilled"
     [[PromiseValue]]: 1
 console-format-es6.js:15 Promise {<pending>}
     __proto__: Promise
diff --git a/third_party/blink/web_tests/paint/filters/absolute-under-clip-under-filter.html b/third_party/blink/web_tests/paint/filters/absolute-under-clip-under-filter.html
deleted file mode 100644
index bb3380cf..0000000
--- a/third_party/blink/web_tests/paint/filters/absolute-under-clip-under-filter.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<!DOCTYPE html>
-Passes if there is a blurred rectangle, left half blue and right half green.
-<div style="filter: blur(10px)">
-  <div style="position: relative; width: 100px; height: 100px; background: blue; overflow: hidden">
-    <div style="position: absolute; width: 100px; height: 100px; right: -50px; background: green"></div>
-  </div>
-</div>
diff --git a/third_party/blink/web_tests/paint/filters/clip-absolute-under-clip-under-filter-expected.html b/third_party/blink/web_tests/paint/filters/clip-absolute-under-clip-under-filter-expected.html
deleted file mode 100644
index ae3e008..0000000
--- a/third_party/blink/web_tests/paint/filters/clip-absolute-under-clip-under-filter-expected.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<!DOCTYPE html>
-Passes if there is a blurred rectangle, left half blue and right half green.
-<div style="filter: blur(10px)">
-  <div style="width: 100px; height: 100px; background: blue">
-    <div style="position: relative; left: 50px; width: 50px; height: 100px; background: green"></div>
-  </div>
-</div>
diff --git a/third_party/blink/web_tests/paint/filters/clip-absolute-under-clip-under-filter.html b/third_party/blink/web_tests/paint/filters/clip-absolute-under-clip-under-filter.html
deleted file mode 100644
index 3aed416..0000000
--- a/third_party/blink/web_tests/paint/filters/clip-absolute-under-clip-under-filter.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<!DOCTYPE html>
-Passes if there is a blurred rectangle, left half blue and right half green.
-<div style="filter: blur(10px)">
-  <div style="position: relative; width: 100px; height: 100px; background: blue; overflow: hidden">
-    <div style="position: absolute; width: 100px; height: 100px; right: -50px; background: green; overflow: hidden"></div>
-  </div>
-</div>
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 3d1692e..3ea4343b 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -4613,6 +4613,11 @@
     getter target
     getter time
     method constructor
+interface IsInputPendingOptions
+    attribute @@toStringTag
+    getter includeContinuous
+    method constructor
+    setter includeContinuous
 interface Keyboard
     attribute @@toStringTag
     method constructor
diff --git a/third_party/bouncycastle/OWNERS b/third_party/bouncycastle/OWNERS
index df1ed5b..8fd43ad6 100644
--- a/third_party/bouncycastle/OWNERS
+++ b/third_party/bouncycastle/OWNERS
@@ -1,5 +1,5 @@
-jbudorick@chromium.org
-mikecase@chromium.org
-yolandyan@chromium.org
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
 
 # COMPONENT: Test>Android
diff --git a/third_party/byte_buddy/OWNERS b/third_party/byte_buddy/OWNERS
index 99c5a7bc..8fd43ad6 100644
--- a/third_party/byte_buddy/OWNERS
+++ b/third_party/byte_buddy/OWNERS
@@ -1,4 +1,5 @@
-jbudorick@chromium.org
-mikecase@chromium.org
-yolandyan@chromium.org
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
+
 # COMPONENT: Test>Android
diff --git a/third_party/checkstyle/OWNERS b/third_party/checkstyle/OWNERS
index efb02801b..e5af2cc 100644
--- a/third_party/checkstyle/OWNERS
+++ b/third_party/checkstyle/OWNERS
@@ -1,5 +1,4 @@
 agrieve@chromium.org
-jbudorick@chromium.org
 nyquist@chromium.org
 
 # COMPONENT: Build
diff --git a/third_party/closure_compiler/externs/automation.js b/third_party/closure_compiler/externs/automation.js
index 8cde2fb..62f27b864 100644
--- a/third_party/closure_compiler/externs/automation.js
+++ b/third_party/closure_compiler/externs/automation.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 // This file was generated by:
-//   tools\json_schema_compiler\compiler.py.
+//   tools/json_schema_compiler/compiler.py.
 // NOTE: The format of types has changed. 'FooType' is now
 //   'chrome.automation.FooType'.
 // Please run the closure compiler before committing changes.
@@ -1441,6 +1441,13 @@
 chrome.automation.AutomationNode.prototype.fontFamily;
 
 /**
+ * Indicates whether this is a root of an editable subtree.
+ * @type {boolean}
+ * @see https://developer.chrome.com/extensions/automation#type-editableRoot
+ */
+chrome.automation.AutomationNode.prototype.editableRoot;
+
+/**
  * Walking the tree.
  * @type {!Array<!chrome.automation.AutomationNode>}
  * @see https://developer.chrome.com/extensions/automation#type-children
diff --git a/third_party/custom_tabs_client/BUILD.gn b/third_party/custom_tabs_client/BUILD.gn
index c2950ac..a9dc309 100644
--- a/third_party/custom_tabs_client/BUILD.gn
+++ b/third_party/custom_tabs_client/BUILD.gn
@@ -132,7 +132,8 @@
     # TODO (bjoyce): Restore to android_support_v7_appcompat_java once source
     # files are manually written to androidx crbug.com/1047843.
     # "//third_party/android_deps:android_support_v7_appcompat_java",
-    "//third_party/android_deps:com_android_support_appcompat_v7_java_orig",
+    # Remove _temp target once upstream is compatible.
+    "//third_party/android_deps:com_android_support_appcompat_v7_java_temp",
     "//third_party/android_deps:com_android_support_collections_java_orig",
     "//third_party/android_deps:com_android_support_interpolator_java",
     "//third_party/android_deps:com_android_support_support_annotations_java",
diff --git a/third_party/espresso/OWNERS b/third_party/espresso/OWNERS
index aa28a39f..8fd43ad6 100644
--- a/third_party/espresso/OWNERS
+++ b/third_party/espresso/OWNERS
@@ -1,3 +1,5 @@
-jbudorick@chromium.org
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
 
 # COMPONENT: Test>Android
diff --git a/third_party/google-truth/OWNERS b/third_party/google-truth/OWNERS
index 8c29a68b..9a85d89 100644
--- a/third_party/google-truth/OWNERS
+++ b/third_party/google-truth/OWNERS
@@ -1,3 +1,3 @@
 fgorski@chromium.org
-jbudorick@chromium.org
+
 # COMPONENT: Test>Android
diff --git a/third_party/guava/OWNERS b/third_party/guava/OWNERS
index ec79990..b1cbdaa 100644
--- a/third_party/guava/OWNERS
+++ b/third_party/guava/OWNERS
@@ -1,5 +1,5 @@
-jbudorick@chromium.org
+bjoyce@chromium.org
+hypan@google.com
 wnwen@chromium.org
-yolandyan@chromium.org
 
-# COMPONENT: Test>Android
\ No newline at end of file
+# COMPONENT: Test>Android
diff --git a/third_party/hamcrest/OWNERS b/third_party/hamcrest/OWNERS
index df1ed5b..8fd43ad6 100644
--- a/third_party/hamcrest/OWNERS
+++ b/third_party/hamcrest/OWNERS
@@ -1,5 +1,5 @@
-jbudorick@chromium.org
-mikecase@chromium.org
-yolandyan@chromium.org
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
 
 # COMPONENT: Test>Android
diff --git a/third_party/icu4j/OWNERS b/third_party/icu4j/OWNERS
index df1ed5b..8fd43ad6 100644
--- a/third_party/icu4j/OWNERS
+++ b/third_party/icu4j/OWNERS
@@ -1,5 +1,5 @@
-jbudorick@chromium.org
-mikecase@chromium.org
-yolandyan@chromium.org
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
 
 # COMPONENT: Test>Android
diff --git a/third_party/jacoco/OWNERS b/third_party/jacoco/OWNERS
index 7e27cb5..4e4b40a 100644
--- a/third_party/jacoco/OWNERS
+++ b/third_party/jacoco/OWNERS
@@ -2,6 +2,6 @@
 aluo@chromium.org
 bjoyce@chromium.org
 yliuyliu@google.com
-yzjr@chromium.org
+yzjr@google.com
 
 # COMPONENT: Tools>CodeCoverage
diff --git a/third_party/junit/OWNERS b/third_party/junit/OWNERS
index 6bc1824..8fd43ad6 100644
--- a/third_party/junit/OWNERS
+++ b/third_party/junit/OWNERS
@@ -1,4 +1,5 @@
-jbudorick@chromium.org
-mikecase@chromium.org
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
 
-# COMPONENT: Tests
+# COMPONENT: Test>Android
diff --git a/third_party/mockito/OWNERS b/third_party/mockito/OWNERS
index 72ff66f..8fd43ad6 100644
--- a/third_party/mockito/OWNERS
+++ b/third_party/mockito/OWNERS
@@ -1,3 +1,5 @@
-jbudorick@chromium.org
-mikecase@chromium.org
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
+
 # COMPONENT: Test>Android
diff --git a/third_party/objenesis/OWNERS b/third_party/objenesis/OWNERS
index df1ed5b..8fd43ad6 100644
--- a/third_party/objenesis/OWNERS
+++ b/third_party/objenesis/OWNERS
@@ -1,5 +1,5 @@
-jbudorick@chromium.org
-mikecase@chromium.org
-yolandyan@chromium.org
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
 
 # COMPONENT: Test>Android
diff --git a/third_party/ow2_asm/OWNERS b/third_party/ow2_asm/OWNERS
index 214acbad..5a51e99c 100644
--- a/third_party/ow2_asm/OWNERS
+++ b/third_party/ow2_asm/OWNERS
@@ -1,5 +1,4 @@
 agrieve@chromium.org
-jbudorick@chromium.org
 smaier@chromium.org
 
 # COMPONENT: Build
diff --git a/third_party/requests/OWNERS b/third_party/requests/OWNERS
index e19bf14..8fd43ad6 100644
--- a/third_party/requests/OWNERS
+++ b/third_party/requests/OWNERS
@@ -1,3 +1,5 @@
-jbudorick@chromium.org
-klundberg@chromium.org
-# COMPONENT: Tools
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
+
+# COMPONENT: Test>Android
diff --git a/third_party/robolectric/OWNERS b/third_party/robolectric/OWNERS
index 1981c0c..f2e9ab5 100644
--- a/third_party/robolectric/OWNERS
+++ b/third_party/robolectric/OWNERS
@@ -1,6 +1,5 @@
 agrieve@chromium.org
 bjoyce@chromium.org
-jbudorick@chromium.org
 wnwen@chromium.org
 
 # COMPONENT: Test>Android
diff --git a/third_party/sqlite4java/OWNERS b/third_party/sqlite4java/OWNERS
index 99c5a7bc..8fd43ad6 100644
--- a/third_party/sqlite4java/OWNERS
+++ b/third_party/sqlite4java/OWNERS
@@ -1,4 +1,5 @@
-jbudorick@chromium.org
-mikecase@chromium.org
-yolandyan@chromium.org
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
+
 # COMPONENT: Test>Android
diff --git a/third_party/ub-uiautomator/OWNERS b/third_party/ub-uiautomator/OWNERS
index cd92aa39..8fd43ad6 100644
--- a/third_party/ub-uiautomator/OWNERS
+++ b/third_party/ub-uiautomator/OWNERS
@@ -1,2 +1,5 @@
-jbudorick@chromium.org
+bjoyce@chromium.org
+hypan@google.com
+yzjr@google.com
+
 # COMPONENT: Test>Android
diff --git a/third_party/webdriver/OWNERS b/third_party/webdriver/OWNERS
index b5f3ea5..3b99d2b 100644
--- a/third_party/webdriver/OWNERS
+++ b/third_party/webdriver/OWNERS
@@ -1,6 +1,4 @@
 # primary reviewer
 johnchen@chromium.org
 
-# backup reviewer
-jbudorick@chromium.org
 # COMPONENT: Test>WebDriver
diff --git a/tools/clang/blink_gc_plugin/BlinkGCPlugin.cpp b/tools/clang/blink_gc_plugin/BlinkGCPlugin.cpp
index 779b06e..a22cb03 100644
--- a/tools/clang/blink_gc_plugin/BlinkGCPlugin.cpp
+++ b/tools/clang/blink_gc_plugin/BlinkGCPlugin.cpp
@@ -37,9 +37,6 @@
         options_.no_members_in_stack_allocated = true;
       } else if (arg == "enable-weak-members-in-unmanaged-classes") {
         options_.enable_weak_members_in_unmanaged_classes = true;
-      } else if (arg == "no-gc-finalized" || arg == "warn-unneeded-finalizer") {
-        // TODO(bikineev): Remove after flags are removed from BUILD.gn.
-        continue;
       } else {
         llvm::errs() << "Unknown blink-gc-plugin argument: " << arg << "\n";
         return false;
diff --git a/tools/clang/scripts/test_tool.py b/tools/clang/scripts/test_tool.py
index 4b384221..acf69d99 100755
--- a/tools/clang/scripts/test_tool.py
+++ b/tools/clang/scripts/test_tool.py
@@ -203,15 +203,15 @@
     print('[ RUN      ] %s' % os.path.relpath(actual))
     expected_output = actual_output = None
     with open(expected, 'r') as f:
-      expected_output = f.read().splitlines()
+      expected_output = f.readlines()
     with open(actual, 'r') as f:
-      actual_output =  f.read().splitlines()
+      actual_output =  f.readlines()
     if actual_output != expected_output:
       failed += 1
-      for line in difflib.unified_diff(expected_output, actual_output,
-                                       fromfile=os.path.relpath(expected),
-                                       tofile=os.path.relpath(actual)):
-        sys.stdout.write(line)
+      lines = difflib.unified_diff(expected_output, actual_output,
+                                   fromfile=os.path.relpath(expected),
+                                   tofile=os.path.relpath(actual))
+      sys.stdout.writelines(lines)
       print('[  FAILED  ] %s' % os.path.relpath(actual))
       # Don't clean up the file on failure, so the results can be referenced
       # more easily.
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 4ee5a15..15d26166 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -19952,6 +19952,7 @@
   <int value="688" label="ManagedGuestSessionAutoLaunchNotificationReduced"/>
   <int value="689" label="SystemFeaturesDisableList"/>
   <int value="690" label="CrostiniArcAdbSideloadingAllowed"/>
+  <int value="691" label="FloatingAccessibilityMenuEnabled"/>
 </enum>
 
 <enum name="EnterprisePolicyDeviceIdValidity">
@@ -26926,6 +26927,28 @@
   <int value="3201" label="WrongBaselineOfButtonElement"/>
   <int value="3202" label="V8Document_HasTrustToken_Method"/>
   <int value="3203" label="ForceLoadAtTop"/>
+  <int value="3204" label="LegacyLayoutByButton"/>
+  <int value="3205" label="LegacyLayoutByDeprecatedFlexBox"/>
+  <int value="3206" label="LegacyLayoutByDetailsMarker"/>
+  <int value="3207" label="LegacyLayoutByEditing"/>
+  <int value="3208" label="LegacyLayoutByFieldSet"/>
+  <int value="3209" label="LegacyLayoutByFileUploadControl"/>
+  <int value="3210" label="LegacyLayoutByFlexBox"/>
+  <int value="3211" label="LegacyLayoutByFrameSet"/>
+  <int value="3212" label="LegacyLayoutByGrid"/>
+  <int value="3213" label="LegacyLayoutByMenuList"/>
+  <int value="3214" label="LegacyLayoutByMultiCol"/>
+  <int value="3215" label="LegacyLayoutByPrinting"/>
+  <int value="3216" label="LegacyLayoutByRuby"/>
+  <int value="3217" label="LegacyLayoutBySVG"/>
+  <int value="3218" label="LegacyLayoutBySlider"/>
+  <int value="3219" label="LegacyLayoutByTable"/>
+  <int value="3220" label="LegacyLayoutByTextCombine"/>
+  <int value="3221" label="LegacyLayoutByTextControl"/>
+  <int value="3222" label="LegacyLayoutByVTTCue"/>
+  <int value="3223" label="LegacyLayoutByWebkitBoxWithoutVerticalLineClamp"/>
+  <int value="3224"
+      label="LegacyLayoutByTableFlexGridBlockInNGFragmentationContext"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
@@ -34652,8 +34675,8 @@
   <int value="47" label="INVALID_STATE_FOR_OPTION"/>
   <int value="49" label="PATCH_INVALID_ARGUMENTS"/>
   <int value="50" label="DIFF_PATCH_SOURCE_MISSING"/>
-  <int value="51" label="UNUSED_BINARIES"/>
-  <int value="52" label="UNUSED_BINARIES_UNINSTALLED"/>
+  <int value="51" label="UNUSED_BINARIES (removed in M84)"/>
+  <int value="52" label="UNUSED_BINARIES_UNINSTALLED (removed in M84)"/>
   <int value="53" label="UNSUPPORTED_OPTION"/>
   <int value="54" label="CPU_NOT_SUPPORTED"/>
   <int value="55" label="REENABLE_UPDATES_SUCCEEDED"/>
@@ -38020,6 +38043,7 @@
   <int value="-1532014193" label="disable-encryption-migration"/>
   <int value="-1529907580" label="ImeServiceConnectable:disabled"/>
   <int value="-1528455406" label="OmniboxPedalSuggestions:enabled"/>
+  <int value="-1524323669" label="InstalledAppsInCbd:enabled"/>
   <int value="-1521296022"
       label="UpdateNotificationScheduleServiceImmediateShowOption:disabled"/>
   <int value="-1521160841" label="DismissNtpPromos:enabled"/>
@@ -40847,6 +40871,7 @@
       label="ClickToCallContextMenuForSelectedText:disabled"/>
   <int value="1794057460" label="AutofillProfileClientValidation:enabled"/>
   <int value="1795186324" label="SyncPseudoUSSExtensionSettings:disabled"/>
+  <int value="1796592669" label="InstalledAppsInCbd:disabled"/>
   <int value="1798347197"
       label="ContextualSuggestionsIPHReverseScroll:disabled"/>
   <int value="1799521026" label="LegacyTLSEnforced:disabled"/>
@@ -59996,8 +60021,8 @@
       label="No previous version archive found for differential update."/>
   <int value="51"
       label="No multi-install products to update. The binaries will be
-             uninstalled if they are not in use."/>
-  <int value="52" label="The binaries were uninstalled."/>
+             uninstalled if they are not in use. (Removed in M84.)"/>
+  <int value="52" label="The binaries were uninstalled. (Removed in M84.)"/>
   <int value="53" label="An unsupported legacy option was given."/>
   <int value="54" label="Current CPU not supported."/>
   <int value="55" label="Autoupdates are now enabled."/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index b983019..690eb2c 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -151739,6 +151739,9 @@
 
 <histogram name="Signin.OAuth2TokenGetFailure" enum="GoogleServiceAuthError"
     expires_after="2020-04-19">
+  <obsolete>
+    Replaced on 2020-04 by Signin.OAuth2TokenGetResult.
+  </obsolete>
   <owner>msarda@chromium.org</owner>
   <owner>droger@chromium.org</owner>
   <summary>
@@ -151746,6 +151749,19 @@
   </summary>
 </histogram>
 
+<histogram name="Signin.OAuth2TokenGetResult" enum="GoogleServiceAuthError"
+    expires_after="never">
+<!-- expires-never: This is needed as long as Chrome is fetching access tokens
+to monitor/debug fetching OAuth2 Token issues. -->
+
+  <owner>msarda@chromium.org</owner>
+  <owner>droger@chromium.org</owner>
+  <summary>
+    Reason fetching an OAuth2 Token failed or none in case of success. Available
+    on all OSes.
+  </summary>
+</histogram>
+
 <histogram name="Signin.OAuth2TokenGetRetry" enum="GoogleServiceAuthError"
     expires_after="never">
 <!-- expires-never: This reports the retry reason for fetching OAuth 2.0 access
@@ -161988,6 +162004,10 @@
 
 <histogram name="Sync.IsNigoriMigratedAfterMigration" enum="Boolean"
     expires_after="2020-04-01">
+  <obsolete>
+    Removed as of 04/2020 because directory implementation of Nigori is no
+    longer used and enough data for corresponding investigation was recorded.
+  </obsolete>
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <summary>
@@ -162076,7 +162096,10 @@
 </histogram>
 
 <histogram name="Sync.LostNavigationCount" units="navigations"
-    expires_after="M85">
+    expires_after="M72">
+  <obsolete>
+    Removed 2019-11.
+  </obsolete>
   <owner>pnoland@chromium.org</owner>
   <summary>
     Counts instances of navigations that are recorded locally but not synced.
@@ -162325,6 +162348,10 @@
 
 <histogram name="Sync.NigoriMigrationAttemptedBeforeNotMigrated" enum="Boolean"
     expires_after="2020-04-01">
+  <obsolete>
+    Removed as of 04/2020 because directory implementation of Nigori is no
+    longer used and enough data for corresponding investigation was recorded.
+  </obsolete>
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <summary>
@@ -162334,10 +162361,13 @@
 </histogram>
 
 <histogram name="Sync.NigoriMigrationReason" enum="SyncNigoriMigrationReason"
-    expires_after="2020-06-07">
+    expires_after="M84">
+  <obsolete>
+    Removed in M84 because directory implementation of Nigori is no longer used
+    and enough data for corresponding investigation was recorded.
+  </obsolete>
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
-  <owner>mamir@chromium.org</owner>
   <summary>
     Records the condition, which triggered Nigori migration to keystore.
   </summary>
@@ -162351,6 +162381,10 @@
 
 <histogram name="Sync.NigoriMigrationTrigger" enum="SyncNigoriMigrationTrigger"
     expires_after="2020-04-01">
+  <obsolete>
+    Removed as of 04/2020 because directory implementation of Nigori is no
+    longer used and enough data for corresponding investigation was recorded.
+  </obsolete>
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <summary>
@@ -163205,10 +163239,13 @@
 </histogram>
 
 <histogram name="Sync.ShouldTriggerMigrationAfterMigration" enum="Boolean"
-    expires_after="2020-06-07">
+    expires_after="M84">
+  <obsolete>
+    Removed in M84 because directory implementation of Nigori is no longer used
+    and enough data for corresponding investigation was recorded.
+  </obsolete>
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
-  <owner>mamir@chromium.org</owner>
   <summary>
     Indicates that Nigori would be migrated again right after migration if
     AttemptToMigrateNigoriToKeystore called.
diff --git a/tools/perf/core/perfetto_binary_roller/BUILD.gn b/tools/perf/core/perfetto_binary_roller/BUILD.gn
index d834bd63..c56f09e 100644
--- a/tools/perf/core/perfetto_binary_roller/BUILD.gn
+++ b/tools/perf/core/perfetto_binary_roller/BUILD.gn
@@ -22,7 +22,7 @@
   }
 
   executable = "upload_trace_processor.py"
-  wrapper_script = "$root_build_dir/upload_trace_processor"
+  wrapper_script = "$root_build_dir/bin/upload_trace_processor"
   executable_args = [
     "--path",
     "@WrappedPath(./$binary_name)",
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps_manager.py b/tools/perf/core/perfetto_binary_roller/binary_deps_manager.py
index ec2f6e7c..6e71262 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps_manager.py
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps_manager.py
@@ -27,16 +27,6 @@
   return os_name
 
 
-def _GetLatestPath(binary_name, platform):
-  with tempfile_ext.NamedTemporaryFile() as latest_file:
-    latest_file.close()
-    remote_path = posixpath.join(CS_FOLDER, binary_name, platform,
-                                 LATEST_FILENAME)
-    cloud_storage.Get(CS_BUCKET, remote_path, latest_file.name)
-    with open(latest_file.name) as latest:
-      return latest.read()
-
-
 def _CalculateHash(remote_path):
   with tempfile_ext.NamedTemporaryFile() as f:
     f.close()
@@ -72,7 +62,23 @@
   _SetLatestPathForBinary(binary_name, platform, remote_path)
 
 
-def SwitchBinaryToLatestVersion(binary_name):
+def GetLatestPath(binary_name, platform):
+  with tempfile_ext.NamedTemporaryFile() as latest_file:
+    latest_file.close()
+    remote_path = posixpath.join(CS_FOLDER, binary_name, platform,
+                                 LATEST_FILENAME)
+    cloud_storage.Get(CS_BUCKET, remote_path, latest_file.name)
+    with open(latest_file.name) as latest:
+      return latest.read()
+
+
+def GetCurrentPath(binary_name, platform):
+  with open(CONFIG_PATH) as f:
+    config = json.load(f)
+  return config[binary_name][platform]['remote_path']
+
+
+def SwitchBinaryToNewPath(binary_name, platform, new_path):
   """Switch the binary version in use to the latest one.
 
   This function updates the config file to contain the path to the latest
@@ -81,10 +87,8 @@
   """
   with open(CONFIG_PATH) as f:
     config = json.load(f)
-  for platform in config[binary_name]:
-    new_path = _GetLatestPath(binary_name, platform)
-    config[binary_name][platform]['remote_path'] = new_path
-    config[binary_name][platform]['hash'] = _CalculateHash(new_path)
+  config[binary_name][platform]['remote_path'] = new_path
+  config[binary_name][platform]['hash'] = _CalculateHash(new_path)
   with open(CONFIG_PATH, 'w') as f:
     json.dump(config, f, indent=4, separators=(',', ': '))
 
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps_manager_unittest.py b/tools/perf/core/perfetto_binary_roller/binary_deps_manager_unittest.py
index 9ad75f7..86fcdbd 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps_manager_unittest.py
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps_manager_unittest.py
@@ -68,7 +68,7 @@
         publicly_readable=True,
     )
 
-  def testSwitchBinaryVersion(self):
+  def testSwitchBinaryToNewPath(self):
     self.writeConfig({'dep': {'testos': {'remote_path': 'old/path/to/bin'}}})
     latest_path = 'new/path/to/bin'
 
@@ -81,7 +81,7 @@
       with mock.patch('py_utils.cloud_storage.CalculateHash') as hash_patch:
         get_patch.side_effect = write_latest_path
         hash_patch.return_value = '123'
-        binary_deps_manager.SwitchBinaryToLatestVersion('dep')
+        binary_deps_manager.SwitchBinaryToNewPath('dep', 'testos', latest_path)
 
     self.assertEqual(
         self.readConfig(),
diff --git a/tools/perf/core/perfetto_binary_roller/roll_trace_processor b/tools/perf/core/perfetto_binary_roller/roll_trace_processor
index 7bc5b93e..0239a34 100755
--- a/tools/perf/core/perfetto_binary_roller/roll_trace_processor
+++ b/tools/perf/core/perfetto_binary_roller/roll_trace_processor
@@ -3,6 +3,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import argparse
 import os
 import sys
 
@@ -18,5 +19,31 @@
 
 
 if __name__ == '__main__':
-  binary_deps_manager.SwitchBinaryToLatestVersion(
-      trace_processor.TP_BINARY_NAME)
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      '--platform', help='linux/mac/win', required=True)
+  parser.add_argument(
+      '--path', metavar='PATH', help='Switch to using a trace processor version'
+      ' stored by this cloud path.')
+  parser.add_argument(
+      '--print-latest', action='store_true',
+      help='Print a cloud path to the latest available version.')
+  parser.add_argument(
+      '--print-current', action='store_true',
+      help='Print a cloud path to the currently used version.')
+
+  args = parser.parse_args()
+
+  if (bool(args.path) + args.print_latest + args.print_current != 1):
+    raise RuntimeError('Please supply exactly one of --path, '
+                       '--print-latest or --print-current.')
+
+  if args.print_latest:
+    print binary_deps_manager.GetLatestPath(
+        trace_processor.TP_BINARY_NAME, args.platform)
+  elif args.print_current:
+    print binary_deps_manager.GetCurrentPath(
+        trace_processor.TP_BINARY_NAME, args.platform)
+  else:
+    binary_deps_manager.SwitchBinaryToNewPath(
+        trace_processor.TP_BINARY_NAME, args.platform, args.path)
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 669443f..f53b19af 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -72,6 +72,8 @@
 crbug.com/986423 [ android-nexus-5x android-webview ] blink_perf.events/EventsDispatchingInDeeplyNestedV1ShadowTrees.html [ Skip ]
 crbug.com/986423 [ android-nexus-5x android-webview ] blink_perf.events/EventsDispatchingInV0ShadowTrees.html [ Skip ]
 crbug.com/986423 [ android-nexus-5x android-webview ] blink_perf.events/EventsDispatchingInV1ShadowTrees.html [ Skip ]
+crbug.com/1069468 blink_perf.events/is-input-pending-all-events.html [ Skip ]
+crbug.com/1069468 blink_perf.events/is-input-pending-default-events.html [ Skip ]
 
 # Benchmark: blink_perf.layout
 crbug.com/551950 [ android-low-end ] blink_perf.layout/* [ Skip ]
diff --git a/ui/accessibility/ax_node.cc b/ui/accessibility/ax_node.cc
index 0db263e3..12f7086 100644
--- a/ui/accessibility/ax_node.cc
+++ b/ui/accessibility/ax_node.cc
@@ -107,67 +107,177 @@
   return deepest_child;
 }
 
+// Search for the next sibling of this node, skipping over any ignored nodes
+// encountered.
+//
+// In our search:
+//   If we find an ignored sibling, we consider its children as our siblings.
+//   If we run out of siblings, we consider an ignored parent's siblings as our
+//     own siblings.
+//
+// Note: this behaviour of 'skipping over' an ignored node makes this subtly
+// different to finding the next (direct) sibling which is unignored.
+//
+// Consider a tree, where (i) marks a node as ignored:
+//
+//   1
+//   ├── 2
+//   ├── 3(i)
+//   │   └── 5
+//   └── 4
+//
+// The next sibling of node 2 is node 3, which is ignored.
+// The next unignored sibling of node 2 could be either:
+//  1) node 4 - next unignored sibling in the literal tree, or
+//  2) node 5 - next unignored sibling in the logical document.
+//
+// There is no next sibling of node 5.
+// The next unignored sibling of node 5 could be either:
+//  1) null   - no next sibling in the literal tree, or
+//  2) node 4 - next unignored sibling in the logical document.
+//
+// In both cases, this method implements approach (2).
+//
+// TODO(chrishall): Can we remove this non-reflexive case by forbidding
+//   GetNextUnignoredSibling calls on an ignored started node?
+// Note: this means that Next/Previous-UnignoredSibling are not reflexive if
+// either of the nodes in question are ignored. From above we get an example:
+//   NextUnignoredSibling(3)     is 4, but
+//   PreviousUnignoredSibling(4) is 5.
+//
+// The view of unignored siblings for node 3 includes both node 2 and node 4:
+//    2 <-- [3(i)] --> 4
+//
+// Whereas nodes 2, 5, and 4 do not consider node 3 to be an unignored sibling:
+// null <-- [2] --> 5
+//    2 <-- [5] --> 4
+//    5 <-- [4] --> null
 AXNode* AXNode::GetNextUnignoredSibling() const {
   DCHECK(!tree_->GetTreeUpdateInProgressState());
-  AXNode* parent_node = parent();
-  size_t index = index_in_parent() + 1;
-  while (parent_node) {
-    if (index < parent_node->children().size()) {
-      AXNode* child = parent_node->children()[index];
-      if (!child->IsIgnored())
-        return child;  // valid position (unignored child)
+  const AXNode* current = this;
 
-      // If the node is ignored, drill down to the ignored node's first child.
-      parent_node = child;
-      index = 0;
+  // If there are children of the |current| node still to consider.
+  bool considerChildren = false;
+
+  while (current) {
+    // A |candidate| sibling to consider.
+    // If it is unignored then we have found our result.
+    // Otherwise promote it to |current| and consider its children.
+    AXNode* candidate;
+
+    if (considerChildren && (candidate = current->GetFirstChild())) {
+      if (!candidate->IsIgnored())
+        return candidate;
+      current = candidate;
+
+    } else if ((candidate = current->GetNextSibling())) {
+      if (!candidate->IsIgnored())
+        return candidate;
+      current = candidate;
+      // Look through the ignored candidate node to consider their children as
+      // though they were siblings.
+      considerChildren = true;
+
     } else {
-      // If the parent is not ignored and we are past all of its children, there
-      // is no next sibling.
-      if (!parent_node->IsIgnored())
+      // Continue our search through a parent iff they are ignored.
+      //
+      // If |current| has an ignored parent, then we consider the parent's
+      // siblings as though they were siblings of |current|.
+      //
+      // Given a tree:
+      //   1
+      //   ├── 2(?)
+      //   │   └── [4]
+      //   └── 3
+      //
+      // Node 4's view of siblings:
+      //   literal tree:   null <-- [4] --> null
+      //
+      // If node 2 is not ignored, then node 4's view doesn't change, and we
+      // have no more nodes to consider:
+      //   unignored tree: null <-- [4] --> null
+      //
+      // If instead node 2 is ignored, then node 4's view of siblings grows to
+      // include node 3, and we have more nodes to consider:
+      //   unignored tree: null <-- [4] --> 3
+      current = current->parent();
+      if (!current || !current->IsIgnored())
         return nullptr;
 
-      // If the parent is ignored and we are past all of its children, continue
-      // on to the parent's next sibling.
-      index = parent_node->index_in_parent() + 1;
-      parent_node = parent_node->parent();
+      // We have already considered all relevant descendants of |current|.
+      considerChildren = false;
     }
   }
+
   return nullptr;
 }
 
+// Search for the previous sibling of this node, skipping over any ignored nodes
+// encountered.
+//
+// In our search for a sibling:
+//   If we find an ignored sibling, we may consider its children as siblings.
+//   If we run out of siblings, we may consider an ignored parent's siblings as
+//     our own.
+//
+// See the documentation for |GetNextUnignoredSibling| for more details.
 AXNode* AXNode::GetPreviousUnignoredSibling() const {
   DCHECK(!tree_->GetTreeUpdateInProgressState());
-  AXNode* parent_node = parent();
-  base::Optional<size_t> index;
-  if (index_in_parent() > 0)
-    index = index_in_parent() - 1;
-  while (parent_node) {
-    if (index.has_value()) {
-      AXNode* child = parent_node->children()[index.value()];
-      if (!child->IsIgnored())
-        return child;  // valid position (unignored child)
+  const AXNode* current = this;
 
-      // If the node is ignored, drill down to the ignored node's last child.
-      parent_node = child;
-      if (parent_node->children().empty())
-        index = base::nullopt;
-      else
-        index = parent_node->children().size() - 1;
+  // If there are children of the |current| node still to consider.
+  bool considerChildren = false;
+
+  while (current) {
+    // A |candidate| sibling to consider.
+    // If it is unignored then we have found our result.
+    // Otherwise promote it to |current| and consider its children.
+    AXNode* candidate;
+
+    if (considerChildren && (candidate = current->GetLastChild())) {
+      if (!candidate->IsIgnored())
+        return candidate;
+      current = candidate;
+
+    } else if ((candidate = current->GetPreviousSibling())) {
+      if (!candidate->IsIgnored())
+        return candidate;
+      current = candidate;
+      // Look through the ignored candidate node to consider their children as
+      // though they were siblings.
+      considerChildren = true;
+
     } else {
-      // If the parent is not ignored and we are past all of its children, there
-      // is no next sibling.
-      if (!parent_node->IsIgnored())
+      // Continue our search through a parent iff they are ignored.
+      //
+      // If |current| has an ignored parent, then we consider the parent's
+      // siblings as though they were siblings of |current|.
+      //
+      // Given a tree:
+      //   1
+      //   ├── 2
+      //   └── 3(?)
+      //       └── [4]
+      //
+      // Node 4's view of siblings:
+      //   literal tree:   null <-- [4] --> null
+      //
+      // If node 3 is not ignored, then node 4's view doesn't change, and we
+      // have no more nodes to consider:
+      //   unignored tree: null <-- [4] --> null
+      //
+      // If instead node 3 is ignored, then node 4's view of siblings grows to
+      // include node 2, and we have more nodes to consider:
+      //   unignored tree:    2 <-- [4] --> null
+      current = current->parent();
+      if (!current || !current->IsIgnored())
         return nullptr;
 
-      // If the parent is ignored and we are past all of its children, continue
-      // on to the parent's previous sibling.
-      if (parent_node->index_in_parent() == 0)
-        index = base::nullopt;
-      else
-        index = parent_node->index_in_parent() - 1;
-      parent_node = parent_node->parent();
+      // We have already considered all relevant descendants of |current|.
+      considerChildren = false;
     }
   }
+
   return nullptr;
 }
 
@@ -208,6 +318,41 @@
   return UnignoredChildIterator(this, nullptr);
 }
 
+// The first (direct) child, ignored or unignored.
+AXNode* AXNode::GetFirstChild() const {
+  if (children().size() == 0)
+    return nullptr;
+  return children()[0];
+}
+
+// The last (direct) child, ignored or unignored.
+AXNode* AXNode::GetLastChild() const {
+  size_t n = children().size();
+  if (n == 0)
+    return nullptr;
+  return children()[n - 1];
+}
+
+// The previous (direct) sibling, ignored or unignored.
+AXNode* AXNode::GetPreviousSibling() const {
+  // Root nodes lack a parent, their index_in_parent should be 0.
+  DCHECK(!parent() ? index_in_parent() == 0 : true);
+  size_t index = index_in_parent();
+  if (index == 0)
+    return nullptr;
+  return parent()->children()[index - 1];
+}
+
+// The next (direct) sibling, ignored or unignored.
+AXNode* AXNode::GetNextSibling() const {
+  if (!parent())
+    return nullptr;
+  size_t nextIndex = index_in_parent() + 1;
+  if (nextIndex >= parent()->children().size())
+    return nullptr;
+  return parent()->children()[nextIndex];
+}
+
 bool AXNode::IsText() const {
   return data().role == ax::mojom::Role::kStaticText ||
          data().role == ax::mojom::Role::kLineBreak ||
diff --git a/ui/accessibility/ax_node.h b/ui/accessibility/ax_node.h
index 8e3f55f..b5c3609 100644
--- a/ui/accessibility/ax_node.h
+++ b/ui/accessibility/ax_node.h
@@ -132,6 +132,13 @@
   UnignoredChildIterator UnignoredChildrenBegin() const;
   UnignoredChildIterator UnignoredChildrenEnd() const;
 
+  // Walking the tree including both ignored and unignored nodes.
+  // These methods consider only the direct children or siblings of a node.
+  AXNode* GetFirstChild() const;
+  AXNode* GetLastChild() const;
+  AXNode* GetPreviousSibling() const;
+  AXNode* GetNextSibling() const;
+
   // Returns true if the node has any of the text related roles.
   bool IsText() const;
 
diff --git a/ui/accessibility/ax_tree_unittest.cc b/ui/accessibility/ax_tree_unittest.cc
index 3cf7532..a8346df3 100644
--- a/ui/accessibility/ax_tree_unittest.cc
+++ b/ui/accessibility/ax_tree_unittest.cc
@@ -2384,6 +2384,309 @@
   EXPECT_EQ(nullptr, tree.GetFromId(16)->GetPreviousUnignoredSibling());
 }
 
+TEST(AXTreeTest, GetSiblingsNoIgnored) {
+  // Since this tree contains no ignored nodes, PreviousSibling and NextSibling
+  // are equivalent to their unignored counterparts.
+  //
+  // 1
+  // ├── 2
+  // │   └── 4
+  // └── 3
+  AXTreeUpdate tree_update;
+  tree_update.root_id = 1;
+  tree_update.nodes.resize(4);
+  tree_update.nodes[0].id = 1;
+  tree_update.nodes[0].child_ids = {2, 3};
+  tree_update.nodes[1].id = 2;
+  tree_update.nodes[1].child_ids = {4};
+  tree_update.nodes[2].id = 3;
+  tree_update.nodes[3].id = 4;
+
+  AXTree tree(tree_update);
+
+  EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(1)->GetNextSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(1)->GetNextUnignoredSibling());
+
+  EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextSibling());
+  EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextUnignoredSibling());
+
+  EXPECT_EQ(tree.GetFromId(2), tree.GetFromId(3)->GetPreviousSibling());
+  EXPECT_EQ(tree.GetFromId(2),
+            tree.GetFromId(3)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(3)->GetNextSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(3)->GetNextUnignoredSibling());
+
+  EXPECT_EQ(nullptr, tree.GetFromId(4)->GetPreviousSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(4)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(4)->GetNextSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(4)->GetNextUnignoredSibling());
+}
+
+TEST(AXTreeTest, GetUnignoredSiblingsChildrenPromoted) {
+  // An ignored node has its' children considered as though they were promoted
+  // to their parents place.
+  //
+  // (i) => node is ignored.
+  //
+  // 1
+  // ├── 2(i)
+  // │   ├── 4
+  // │   └── 5
+  // └── 3
+  AXTreeUpdate tree_update;
+  tree_update.root_id = 1;
+  tree_update.nodes.resize(5);
+  tree_update.nodes[0].id = 1;
+  tree_update.nodes[0].child_ids = {2, 3};
+  tree_update.nodes[1].id = 2;
+  tree_update.nodes[1].AddState(ax::mojom::State::kIgnored);
+  tree_update.nodes[1].child_ids = {4, 5};
+  tree_update.nodes[2].id = 3;
+  tree_update.nodes[3].id = 4;
+  tree_update.nodes[4].id = 5;
+
+  AXTree tree(tree_update);
+
+  // Root node has no siblings.
+  EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousUnignoredSibling());
+
+  // Node 2's view of siblings:
+  // literal tree:   null <-- [2(i)] --> 3
+  // unignored tree: null <-- [2(i)] --> 3
+  EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextSibling());
+  EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextUnignoredSibling());
+
+  // Node 3's view of siblings:
+  // literal tree:   2(i) <-- [3] --> null
+  // unignored tree:    5 <-- [4] --> null
+  EXPECT_EQ(tree.GetFromId(2), tree.GetFromId(3)->GetPreviousSibling());
+  EXPECT_EQ(tree.GetFromId(5),
+            tree.GetFromId(3)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(3)->GetNextSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(3)->GetNextUnignoredSibling());
+
+  // Node 4's view of siblings:
+  // literal tree:   null <-- [4] --> 5
+  // unignored tree: null <-- [4] --> 5
+  EXPECT_EQ(nullptr, tree.GetFromId(4)->GetPreviousSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(4)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(tree.GetFromId(5), tree.GetFromId(4)->GetNextSibling());
+  EXPECT_EQ(tree.GetFromId(5), tree.GetFromId(4)->GetNextUnignoredSibling());
+
+  // Node 5's view of siblings:
+  // literal tree:   4 <-- [5] --> null
+  // unignored tree: 4 <-- [5] --> 3
+  EXPECT_EQ(tree.GetFromId(4), tree.GetFromId(5)->GetPreviousSibling());
+  EXPECT_EQ(tree.GetFromId(4),
+            tree.GetFromId(5)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(5)->GetNextSibling());
+  EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(5)->GetNextUnignoredSibling());
+}
+
+TEST(AXTreeTest, GetUnignoredSiblingsIgnoredChildSkipped) {
+  // Ignored children of ignored parents are skipped over.
+  //
+  // (i) => node is ignored.
+  //
+  // 1
+  // ├── 2(i)
+  // │   ├── 4
+  // │   └── 5(i)
+  // └── 3
+  AXTreeUpdate tree_update;
+  tree_update.root_id = 1;
+  tree_update.nodes.resize(5);
+  tree_update.nodes[0].id = 1;
+  tree_update.nodes[0].child_ids = {2, 3};
+  tree_update.nodes[1].id = 2;
+  tree_update.nodes[1].AddState(ax::mojom::State::kIgnored);
+  tree_update.nodes[1].child_ids = {4, 5};
+  tree_update.nodes[2].id = 3;
+  tree_update.nodes[3].id = 4;
+  tree_update.nodes[4].id = 5;
+  tree_update.nodes[4].AddState(ax::mojom::State::kIgnored);
+
+  AXTree tree(tree_update);
+
+  // Root node has no siblings.
+  EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(1)->GetNextUnignoredSibling());
+
+  // Node 2's view of siblings:
+  // literal tree:   null <-- [2(i)] --> 3
+  // unignored tree: null <-- [2(i)] --> 3
+  EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextSibling());
+  EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextUnignoredSibling());
+
+  // Node 3's view of siblings:
+  // literal tree:   2(i) <-- [3] --> null
+  // unignored tree:    4 <-- [3] --> null
+  EXPECT_EQ(tree.GetFromId(2), tree.GetFromId(3)->GetPreviousSibling());
+  EXPECT_EQ(tree.GetFromId(4),
+            tree.GetFromId(3)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(3)->GetNextSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(3)->GetNextUnignoredSibling());
+
+  // Node 4's view of siblings:
+  // literal tree:   null <-- [4] --> 5(i)
+  // unignored tree: null <-- [4] --> 3
+  EXPECT_EQ(nullptr, tree.GetFromId(4)->GetPreviousSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(4)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(tree.GetFromId(5), tree.GetFromId(4)->GetNextSibling());
+  EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(4)->GetNextUnignoredSibling());
+
+  // Node 5's view of siblings:
+  // literal tree:   4 <-- [5(i)] --> null
+  // unignored tree: 4 <-- [5(i)] --> 3
+  EXPECT_EQ(tree.GetFromId(4), tree.GetFromId(5)->GetPreviousSibling());
+  EXPECT_EQ(tree.GetFromId(4),
+            tree.GetFromId(5)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(5)->GetNextSibling());
+  EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(5)->GetNextUnignoredSibling());
+}
+
+TEST(AXTreeTest, GetUnignoredSiblingIgnoredParentIrrelevant) {
+  // An ignored parent is not relevant unless the search would need to continue
+  // up through it.
+  //
+  // (i) => node is ignored.
+  //
+  // 1(i)
+  // ├── 2
+  // └── 3
+  AXTreeUpdate tree_update;
+  tree_update.root_id = 1;
+  tree_update.nodes.resize(3);
+  tree_update.nodes[0].id = 1;
+  tree_update.nodes[0].AddState(ax::mojom::State::kIgnored);
+  tree_update.nodes[0].child_ids = {2, 3};
+  tree_update.nodes[1].id = 2;
+  tree_update.nodes[2].id = 3;
+
+  AXTree tree(tree_update);
+
+  // Node 2 and 3 are each other's unignored siblings, the parent's ignored
+  // status is not relevant for this search.
+  EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextUnignoredSibling());
+  EXPECT_EQ(tree.GetFromId(2),
+            tree.GetFromId(3)->GetPreviousUnignoredSibling());
+}
+
+TEST(AXTreeTest, GetUnignoredSiblingsAllIgnored) {
+  // Test termination when all nodes, including the root node, are ignored.
+  //
+  // (i) => node is ignored.
+  //
+  // 1(i)
+  // └── 2(i)
+  AXTreeUpdate tree_update;
+  tree_update.root_id = 1;
+  tree_update.nodes.resize(2);
+  tree_update.nodes[0].id = 1;
+  tree_update.nodes[0].AddState(ax::mojom::State::kIgnored);
+  tree_update.nodes[0].child_ids = {2};
+  tree_update.nodes[1].id = 2;
+  tree_update.nodes[1].AddState(ax::mojom::State::kIgnored);
+
+  AXTree tree(tree_update);
+
+  EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(1)->GetNextUnignoredSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(2)->GetNextUnignoredSibling());
+}
+
+TEST(AXTreeTest, GetUnignoredSiblingsNestedIgnored) {
+  // Test promotion of children through multiple layers of ignored parents.
+  // (i) => node is ignored.
+  //
+  // 1
+  // ├── 2
+  // ├── 3(i)
+  // │   └── 5(i)
+  // │       └── 6
+  // └── 4
+  AXTreeUpdate tree_update;
+  tree_update.root_id = 1;
+  tree_update.nodes.resize(6);
+  tree_update.nodes[0].id = 1;
+  tree_update.nodes[0].child_ids = {2, 3, 4};
+  tree_update.nodes[1].id = 2;
+  tree_update.nodes[2].id = 3;
+  tree_update.nodes[2].AddState(ax::mojom::State::kIgnored);
+  tree_update.nodes[2].child_ids = {5};
+  tree_update.nodes[3].id = 4;
+  tree_update.nodes[4].id = 5;
+  tree_update.nodes[4].AddState(ax::mojom::State::kIgnored);
+  tree_update.nodes[4].child_ids = {6};
+  tree_update.nodes[5].id = 6;
+
+  AXTree tree(tree_update);
+
+  EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousUnignoredSibling());
+
+  const AXNode* node2 = tree.GetFromId(2);
+  const AXNode* node3 = tree.GetFromId(3);
+  const AXNode* node4 = tree.GetFromId(4);
+  const AXNode* node5 = tree.GetFromId(5);
+  const AXNode* node6 = tree.GetFromId(6);
+
+  ASSERT_NE(nullptr, node2);
+  ASSERT_NE(nullptr, node3);
+  ASSERT_NE(nullptr, node4);
+  ASSERT_NE(nullptr, node5);
+  ASSERT_NE(nullptr, node6);
+
+  // Node 2's view of siblings:
+  // literal tree:   null <-- [2] --> 3
+  // unignored tree: null <-- [2] --> 6
+  EXPECT_EQ(nullptr, node2->GetPreviousSibling());
+  EXPECT_EQ(nullptr, node2->GetPreviousUnignoredSibling());
+  EXPECT_EQ(node3, node2->GetNextSibling());
+  EXPECT_EQ(node6, node2->GetNextUnignoredSibling());
+
+  // Node 3's view of siblings:
+  // literal tree:   2 <-- [3(i)] --> 4
+  // unignored tree: 2 <-- [3(i)] --> 4
+  EXPECT_EQ(node2, node3->GetPreviousSibling());
+  EXPECT_EQ(node2, node3->GetPreviousUnignoredSibling());
+  EXPECT_EQ(node4, node3->GetNextSibling());
+  EXPECT_EQ(node4, node3->GetNextUnignoredSibling());
+
+  // Node 4's view of siblings:
+  // literal tree:   3 <-- [4] --> null
+  // unignored tree: 6 <-- [4] --> null
+  EXPECT_EQ(node3, node4->GetPreviousSibling());
+  EXPECT_EQ(node6, node4->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, node4->GetNextSibling());
+  EXPECT_EQ(nullptr, node4->GetNextUnignoredSibling());
+
+  // Node 5's view of siblings:
+  // literal tree:   null <-- [5(i)] --> null
+  // unignored tree:    2 <-- [5(i)] --> 4
+  EXPECT_EQ(nullptr, node5->GetPreviousSibling());
+  EXPECT_EQ(node2, node5->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, node5->GetNextSibling());
+  EXPECT_EQ(node4, node5->GetNextUnignoredSibling());
+
+  // Node 6's view of siblings:
+  // literal tree:   null <-- [6] --> null
+  // unignored tree:    2 <-- [6] --> 4
+  EXPECT_EQ(nullptr, node6->GetPreviousSibling());
+  EXPECT_EQ(node2, node6->GetPreviousUnignoredSibling());
+  EXPECT_EQ(nullptr, node6->GetNextSibling());
+  EXPECT_EQ(node4, node6->GetNextUnignoredSibling());
+}
+
 TEST(AXTreeTest, UnignoredSelection) {
   AXTreeUpdate tree_update;
   // (i) => node is ignored
@@ -2566,6 +2869,50 @@
   TEST_SELECTION(tree_update, test_ax_tree_manager.GetTree(), input, expected);
 }
 
+TEST(AXTreeTest, GetChildrenOrSiblings) {
+  // 1
+  // ├── 2
+  // │   └── 5
+  // ├── 3
+  // └── 4
+  AXTreeUpdate tree_update;
+  tree_update.root_id = 1;
+  tree_update.nodes.resize(5);
+  tree_update.nodes[0].id = 1;
+  tree_update.nodes[0].child_ids = {2, 3, 4};
+  tree_update.nodes[1].id = 2;
+  tree_update.nodes[1].child_ids = {5};
+  tree_update.nodes[2].id = 3;
+  tree_update.nodes[3].id = 4;
+  tree_update.nodes[4].id = 5;
+
+  AXTree tree(tree_update);
+
+  EXPECT_EQ(tree.GetFromId(2), tree.GetFromId(1)->GetFirstChild());
+  EXPECT_EQ(tree.GetFromId(5), tree.GetFromId(2)->GetFirstChild());
+  EXPECT_EQ(nullptr, tree.GetFromId(3)->GetFirstChild());
+  EXPECT_EQ(nullptr, tree.GetFromId(4)->GetFirstChild());
+  EXPECT_EQ(nullptr, tree.GetFromId(5)->GetFirstChild());
+
+  EXPECT_EQ(tree.GetFromId(4), tree.GetFromId(1)->GetLastChild());
+  EXPECT_EQ(tree.GetFromId(5), tree.GetFromId(2)->GetLastChild());
+  EXPECT_EQ(nullptr, tree.GetFromId(3)->GetLastChild());
+  EXPECT_EQ(nullptr, tree.GetFromId(4)->GetLastChild());
+  EXPECT_EQ(nullptr, tree.GetFromId(5)->GetLastChild());
+
+  EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousSibling());
+  EXPECT_EQ(tree.GetFromId(2), tree.GetFromId(3)->GetPreviousSibling());
+  EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(4)->GetPreviousSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(5)->GetPreviousSibling());
+
+  EXPECT_EQ(nullptr, tree.GetFromId(1)->GetNextSibling());
+  EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextSibling());
+  EXPECT_EQ(tree.GetFromId(4), tree.GetFromId(3)->GetNextSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(4)->GetNextSibling());
+  EXPECT_EQ(nullptr, tree.GetFromId(5)->GetNextSibling());
+}
+
 TEST(AXTreeTest, ChildTreeIds) {
   ui::AXTreeID tree_id_1 = ui::AXTreeID::CreateNewAXTreeID();
   ui::AXTreeID tree_id_2 = ui::AXTreeID::CreateNewAXTreeID();
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.cc b/ui/accessibility/platform/ax_platform_node_auralinux.cc
index d5931fc..2d57de6 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -3897,6 +3897,7 @@
       OnCheckedStateChanged();
       break;
     case ax::mojom::Event::kExpandedChanged:
+    case ax::mojom::Event::kStateChanged:
       OnExpandedStateChanged(GetData().HasState(ax::mojom::State::kExpanded));
       break;
     case ax::mojom::Event::kFocus:
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index 6881b1b..c7a33acd 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -7136,6 +7136,7 @@
       return EVENT_SYSTEM_ALERT;
     case ax::mojom::Event::kCheckedStateChanged:
     case ax::mojom::Event::kExpandedChanged:
+    case ax::mojom::Event::kStateChanged:
       return EVENT_OBJECT_STATECHANGE;
     case ax::mojom::Event::kFocus:
     case ax::mojom::Event::kFocusContext:
@@ -7212,6 +7213,9 @@
       return UIA_ControllerForPropertyId;
     case ax::mojom::Event::kCheckedStateChanged:
       return UIA_ToggleToggleStatePropertyId;
+    case ax::mojom::Event::kRowCollapsed:
+    case ax::mojom::Event::kRowExpanded:
+      return UIA_ExpandCollapseExpandCollapseStatePropertyId;
     default:
       return base::nullopt;
   }
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index c1bc5e0..e24de3f 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -123,6 +123,8 @@
     "models/combobox_model.cc",
     "models/combobox_model.h",
     "models/combobox_model_observer.h",
+    "models/image_model.cc",
+    "models/image_model.h",
     "models/list_model.h",
     "models/list_model_observer.h",
     "models/list_selection_model.cc",
@@ -895,6 +897,7 @@
     "l10n/l10n_util_unittest.cc",
     "l10n/time_format_unittest.cc",
     "layout_unittest.cc",
+    "models/image_model_unittest.cc",
     "models/simple_menu_model_unittest.cc",
     "models/tree_node_iterator_unittest.cc",
     "pointer/touch_ui_controller_unittest.cc",
diff --git a/ui/base/cocoa/menu_controller.mm b/ui/base/cocoa/menu_controller.mm
index d50bbf6..56b1468 100644
--- a/ui/base/cocoa/menu_controller.mm
+++ b/ui/base/cocoa/menu_controller.mm
@@ -11,6 +11,7 @@
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/accelerators/platform_accelerator_cocoa.h"
 #include "ui/base/l10n/l10n_util_mac.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/models/simple_menu_model.h"
 #import "ui/events/event_utils.h"
 #include "ui/gfx/font_list.h"
@@ -191,9 +192,9 @@
       keyEquivalent:@""]);
 
   // If the menu item has an icon, set it.
-  gfx::Image icon;
-  if (model->GetIconAt(index, &icon) && !icon.IsEmpty())
-    [item setImage:icon.ToNSImage()];
+  ui::ImageModel icon = model->GetIconAt(index);
+  if (icon.IsImage())
+    [item setImage:icon.GetImage().ToNSImage()];
 
   ui::MenuModel::ItemType type = model->GetTypeAt(index);
   if (type == ui::MenuModel::TYPE_SUBMENU && model->IsVisibleAt(index)) {
@@ -263,9 +264,8 @@
         l10n_util::FixUpWindowsStyleLabel(model->GetLabelAt(modelIndex));
     [(id)item setTitle:label];
 
-    gfx::Image icon;
-    model->GetIconAt(modelIndex, &icon);
-    [(id)item setImage:icon.IsEmpty() ? nil : icon.ToNSImage()];
+    ui::ImageModel icon = model->GetIconAt(modelIndex);
+    [(id)item setImage:icon.IsImage() ? icon.GetImage().ToNSImage() : nil];
   }
   const gfx::FontList* font_list = model->GetLabelFontListAt(modelIndex);
   if (font_list) {
diff --git a/ui/base/models/image_model.cc b/ui/base/models/image_model.cc
new file mode 100644
index 0000000..501f8dc
--- /dev/null
+++ b/ui/base/models/image_model.cc
@@ -0,0 +1,97 @@
+// 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 "ui/base/models/image_model.h"
+
+namespace ui {
+
+VectorIconModel::VectorIconModel() = default;
+
+VectorIconModel::VectorIconModel(const gfx::VectorIcon& vector_icon,
+                                 int color_id,
+                                 int icon_size)
+    : vector_icon_(&vector_icon), icon_size_(icon_size), color_id_(color_id) {}
+
+VectorIconModel::VectorIconModel(const gfx::VectorIcon& vector_icon,
+                                 SkColor color,
+                                 int icon_size)
+    : vector_icon_(&vector_icon), icon_size_(icon_size), color_(color) {}
+
+VectorIconModel::~VectorIconModel() = default;
+
+VectorIconModel::VectorIconModel(const VectorIconModel&) = default;
+
+VectorIconModel& VectorIconModel::operator=(const VectorIconModel&) = default;
+
+VectorIconModel::VectorIconModel(VectorIconModel&&) = default;
+
+VectorIconModel& VectorIconModel::operator=(VectorIconModel&&) = default;
+
+ImageModel::ImageModel() = default;
+
+ImageModel::ImageModel(const VectorIconModel& vector_icon_model)
+    : vector_icon_model_(vector_icon_model) {}
+
+ImageModel::ImageModel(const gfx::Image& image) : image_(image) {}
+
+ImageModel::ImageModel(const gfx::ImageSkia& image_skia)
+    : ImageModel(gfx::Image(image_skia)) {}
+
+ImageModel::~ImageModel() = default;
+
+ImageModel::ImageModel(const ImageModel&) = default;
+
+ImageModel& ImageModel::operator=(const ImageModel&) = default;
+
+ImageModel::ImageModel(ImageModel&&) = default;
+
+ImageModel& ImageModel::operator=(ImageModel&&) = default;
+
+// static
+ImageModel ImageModel::FromVectorIcon(const gfx::VectorIcon& vector_icon,
+                                      int color_id,
+                                      int icon_size) {
+  return ImageModel(VectorIconModel(vector_icon, color_id, icon_size));
+}
+
+// static
+ImageModel ImageModel::FromVectorIcon(const gfx::VectorIcon& vector_icon,
+                                      SkColor color,
+                                      int icon_size) {
+  return ImageModel(VectorIconModel(vector_icon, color, icon_size));
+}
+
+// static
+ImageModel ImageModel::FromImage(const gfx::Image& image) {
+  return ImageModel(image);
+}
+
+// static
+ImageModel ImageModel::FromImageSkia(const gfx::ImageSkia& image_skia) {
+  return ImageModel(image_skia);
+}
+
+bool ImageModel::IsEmpty() const {
+  return !IsVectorIcon() && !IsImage();
+}
+
+bool ImageModel::IsVectorIcon() const {
+  return vector_icon_model_ && !vector_icon_model_.value().is_empty();
+}
+
+bool ImageModel::IsImage() const {
+  return image_ && !image_.value().IsEmpty();
+}
+
+const VectorIconModel ImageModel::GetVectorIcon() const {
+  DCHECK(IsVectorIcon());
+  return vector_icon_model_.value();
+}
+
+const gfx::Image ImageModel::GetImage() const {
+  DCHECK(IsImage());
+  return image_.value();
+}
+
+}  // namespace ui
\ No newline at end of file
diff --git a/ui/base/models/image_model.h b/ui/base/models/image_model.h
new file mode 100644
index 0000000..098a92ed
--- /dev/null
+++ b/ui/base/models/image_model.h
@@ -0,0 +1,113 @@
+// 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 UI_BASE_MODELS_IMAGE_MODEL_H_
+#define UI_BASE_MODELS_IMAGE_MODEL_H_
+
+#include "base/callback.h"
+#include "base/optional.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/ui_base_export.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace gfx {
+struct VectorIcon;
+}  // namespace gfx
+
+namespace ui {
+
+// The following classes encapsulate the various ways that a model may provide
+// or otherwise specify an icon or image. Most notably, these are used by the
+// MenuModel and SimpleMenuModel for building actual menus.
+//
+// The VectorIconModel represents the combination of the icon path and its
+// optional color id. The optional color is provided by the color id which is
+// eventually resolved by the ColorProvider from the correct context. This class
+// is only used internal to ImageModel and should never be instantiated except
+// by ImageModel.
+
+class UI_BASE_EXPORT VectorIconModel {
+ public:
+  VectorIconModel();
+  VectorIconModel(const VectorIconModel&);
+  VectorIconModel& operator=(const VectorIconModel&);
+  VectorIconModel(VectorIconModel&&);
+  VectorIconModel& operator=(VectorIconModel&&);
+  ~VectorIconModel();
+
+  bool is_empty() const { return !vector_icon_; }
+
+ private:
+  friend class ThemedVectorIcon;
+  friend class ImageModel;
+
+  VectorIconModel(const gfx::VectorIcon& vector_icon,
+                  int color_id,
+                  int icon_size);
+  // TODO (kylixrd): This should be eventually removed once all instances of
+  // hard-coded SkColor constants are removed in favor of using a color id.
+  VectorIconModel(const gfx::VectorIcon& vector_icon,
+                  SkColor color,
+                  int icon_size);
+
+  const gfx::VectorIcon* vector_icon() const { return vector_icon_; }
+  int icon_size() const { return icon_size_; }
+  int color_id() const { return color_id_.value(); }
+  SkColor color() const { return color_.value(); }
+  bool has_color() const { return color_.has_value(); }
+
+  const gfx::VectorIcon* vector_icon_ = nullptr;
+  int icon_size_ = 0;
+  // Only one of the following will ever be assigned.
+  // TODO: Update to use std::variant or base:Variant once one of them is
+  // available to use.
+  base::Optional<int> color_id_;
+  base::Optional<SkColor> color_;
+};
+
+// ImageModel encapsulates either a gfx::Image or a VectorIconModel. Only one
+// of the two may be specified at a given time. This class is instantiated via
+// the FromXXXX static factory functions.
+
+class UI_BASE_EXPORT ImageModel {
+ public:
+  ImageModel();
+  ImageModel(const ImageModel&);
+  ImageModel& operator=(const ImageModel&);
+  ImageModel(ImageModel&&);
+  ImageModel& operator=(ImageModel&&);
+  ~ImageModel();
+
+  static ImageModel FromVectorIcon(const gfx::VectorIcon& vector_icon,
+                                   int color_id = -1,
+                                   int icon_size = 0);
+  static ImageModel FromVectorIcon(const gfx::VectorIcon& vector_icon,
+                                   SkColor color,
+                                   int icon_size = 0);
+  static ImageModel FromImage(const gfx::Image& image);
+  static ImageModel FromImageSkia(const gfx::ImageSkia& image_skia);
+
+  bool IsEmpty() const;
+  bool IsVectorIcon() const;
+  bool IsImage() const;
+  // Only valid if IsVectorIcon() or IsImage() return true, respectively.
+  const VectorIconModel GetVectorIcon() const;
+  const gfx::Image GetImage() const;
+
+ private:
+  ImageModel(const gfx::Image& image);
+  ImageModel(const gfx::ImageSkia& image_skia);
+  ImageModel(const VectorIconModel& vector_icon_model);
+
+  // Only one of the following will ever be assigned.
+  // TODO: Update to use std::variant or base:Variant once one of them is
+  // available to use.
+  base::Optional<VectorIconModel> vector_icon_model_;
+  base::Optional<gfx::Image> image_;
+};
+
+}  // namespace ui
+
+#endif  // UI_BASE_MODELS_IMAGE_MODEL_H_
diff --git a/ui/base/models/image_model_unittest.cc b/ui/base/models/image_model_unittest.cc
new file mode 100644
index 0000000..3b494797
--- /dev/null
+++ b/ui/base/models/image_model_unittest.cc
@@ -0,0 +1,95 @@
+// 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 "ui/base/models/image_model.h"
+
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/image/image_unittest_util.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/gfx/vector_icon_types.h"
+
+namespace ui {
+
+namespace {
+
+const gfx::VectorIcon& GetVectorIcon() {
+  static constexpr gfx::PathElement path[] = {gfx::CommandType::CIRCLE, 24, 18,
+                                              5};
+  static const gfx::VectorIconRep rep[] = {{path, 4}};
+  static constexpr gfx::VectorIcon circle_icon = {rep, 1, "circle"};
+
+  return circle_icon;
+}
+
+}  // namespace
+
+TEST(ImageModelTest, DefaultEmpty) {
+  ImageModel image_model;
+
+  EXPECT_TRUE(image_model.IsEmpty());
+}
+
+TEST(ImageModelTest, DefaultVectorIconEmpty) {
+  VectorIconModel vector_icon_model;
+
+  EXPECT_TRUE(vector_icon_model.is_empty());
+}
+
+TEST(ImageModelTest, CheckForVectorIcon) {
+  ImageModel image_model = ImageModel::FromVectorIcon(GetVectorIcon());
+
+  EXPECT_FALSE(image_model.IsEmpty());
+  EXPECT_TRUE(image_model.IsVectorIcon());
+}
+
+TEST(ImageModelTest, CheckForImage) {
+  ImageModel image_model =
+      ImageModel::FromImage(gfx::test::CreateImage(16, 16));
+
+  EXPECT_FALSE(image_model.IsEmpty());
+  EXPECT_TRUE(image_model.IsImage());
+}
+
+TEST(ImageModelTest, CheckAssignVectorIcon) {
+  VectorIconModel vector_icon_model_dest;
+  VectorIconModel vector_icon_model_src =
+      ImageModel::FromVectorIcon(GetVectorIcon()).GetVectorIcon();
+
+  EXPECT_TRUE(vector_icon_model_dest.is_empty());
+  EXPECT_FALSE(vector_icon_model_src.is_empty());
+
+  vector_icon_model_dest = vector_icon_model_src;
+  EXPECT_FALSE(vector_icon_model_dest.is_empty());
+}
+
+TEST(ImageModelTest, CheckAssignImage) {
+  ImageModel image_model_dest;
+  ImageModel image_model_src =
+      ImageModel::FromImage(gfx::test::CreateImage(16, 16));
+
+  EXPECT_TRUE(image_model_dest.IsEmpty());
+  EXPECT_FALSE(image_model_src.IsEmpty());
+  EXPECT_TRUE(image_model_src.IsImage());
+  EXPECT_FALSE(image_model_src.IsVectorIcon());
+
+  image_model_dest = image_model_src;
+
+  EXPECT_FALSE(image_model_dest.IsEmpty());
+  EXPECT_TRUE(image_model_dest.IsImage());
+  EXPECT_FALSE(image_model_dest.IsVectorIcon());
+
+  image_model_src = ImageModel::FromVectorIcon(GetVectorIcon());
+
+  EXPECT_TRUE(image_model_src.IsVectorIcon());
+
+  image_model_dest = image_model_src;
+
+  EXPECT_TRUE(image_model_dest.IsVectorIcon());
+  EXPECT_FALSE(image_model_dest.IsImage());
+}
+
+}  // namespace ui
diff --git a/ui/base/models/menu_model.cc b/ui/base/models/menu_model.cc
index 73f0ab6..90a350c 100644
--- a/ui/base/models/menu_model.cc
+++ b/ui/base/models/menu_model.cc
@@ -4,6 +4,8 @@
 
 #include "ui/base/models/menu_model.h"
 
+#include "ui/base/models/image_model.h"
+
 namespace ui {
 
 MenuModel::MenuModel() : menu_model_delegate_(nullptr) {}
@@ -50,15 +52,11 @@
   return base::string16();
 }
 
-const gfx::VectorIcon* MenuModel::GetMinorIconAt(int index) const {
-  return nullptr;
+ImageModel MenuModel::GetMinorIconAt(int index) const {
+  return ImageModel();
 }
 
 const gfx::FontList* MenuModel::GetLabelFontListAt(int index) const {
-  return NULL;
-}
-
-const gfx::VectorIcon* MenuModel::GetVectorIconAt(int index) const {
   return nullptr;
 }
 
diff --git a/ui/base/models/menu_model.h b/ui/base/models/menu_model.h
index 5bcc620..887eee6 100644
--- a/ui/base/models/menu_model.h
+++ b/ui/base/models/menu_model.h
@@ -10,19 +10,17 @@
 #include "ui/base/models/menu_model_delegate.h"
 #include "ui/base/models/menu_separator_types.h"
 #include "ui/base/ui_base_export.h"
-#include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/native_widget_types.h"
 
 namespace gfx {
 class FontList;
-class Image;
-struct VectorIcon;
 }
 
 namespace ui {
 
 class Accelerator;
 class ButtonMenuItemModel;
+class ImageModel;
 
 // An interface implemented by an object that provides the content of a menu.
 class UI_BASE_EXPORT MenuModel : public base::SupportsWeakPtr<MenuModel> {
@@ -73,7 +71,7 @@
 
   // Returns the minor icon of the item at the specified index. The minor icon
   // is rendered to the left of the minor text.
-  virtual const gfx::VectorIcon* GetMinorIconAt(int index) const;
+  virtual ImageModel GetMinorIconAt(int index) const;
 
   // Returns true if the menu item (label/sublabel/icon) at the specified
   // index can change over the course of the menu's lifetime. If this function
@@ -97,13 +95,9 @@
   // index belongs to.
   virtual int GetGroupIdAt(int index) const = 0;
 
-  // Gets the icon for the item at the specified index, returning true if there
-  // is an icon, false otherwise.
-  virtual bool GetIconAt(int index, gfx::Image* icon) const = 0;
-
-  // Gets the vector icon for the item at the specified index. At most one of
-  // GetIconAt() and GetVectorIconAt() should be used for a single menu index.
-  virtual const gfx::VectorIcon* GetVectorIconAt(int index) const;
+  // Gets the icon for the item at the specified index. ImageModel is empty if
+  // there is no icon.
+  virtual ImageModel GetIconAt(int index) const = 0;
 
   // Returns the model for a menu item with a line of buttons at |index|.
   virtual ButtonMenuItemModel* GetButtonMenuItemAt(int index) const = 0;
diff --git a/ui/base/models/simple_menu_model.cc b/ui/base/models/simple_menu_model.cc
index d5d058f..6f7faa9 100644
--- a/ui/base/models/simple_menu_model.cc
+++ b/ui/base/models/simple_menu_model.cc
@@ -12,6 +12,9 @@
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/vector_icon_types.h"
 
 namespace ui {
 
@@ -95,29 +98,15 @@
 
 void SimpleMenuModel::AddItemWithIcon(int command_id,
                                       const base::string16& label,
-                                      const gfx::ImageSkia& icon) {
+                                      const ImageModel& icon) {
   Item item(command_id, TYPE_COMMAND, label);
-  item.icon = gfx::Image(icon);
-  AppendItem(std::move(item));
-}
-
-void SimpleMenuModel::AddItemWithIcon(int command_id,
-                                      const base::string16& label,
-                                      const gfx::VectorIcon& icon) {
-  Item item(command_id, TYPE_COMMAND, label);
-  item.vector_icon = &icon;
+  item.icon = icon;
   AppendItem(std::move(item));
 }
 
 void SimpleMenuModel::AddItemWithStringIdAndIcon(int command_id,
                                                  int string_id,
-                                                 const gfx::ImageSkia& icon) {
-  AddItemWithIcon(command_id, l10n_util::GetStringUTF16(string_id), icon);
-}
-
-void SimpleMenuModel::AddItemWithStringIdAndIcon(int command_id,
-                                                 int string_id,
-                                                 const gfx::VectorIcon& icon) {
+                                                 const ImageModel& icon) {
   AddItemWithIcon(command_id, l10n_util::GetStringUTF16(string_id), icon);
 }
 
@@ -145,9 +134,9 @@
 
 void SimpleMenuModel::AddHighlightedItemWithIcon(int command_id,
                                                  const base::string16& label,
-                                                 const gfx::ImageSkia& icon) {
+                                                 const ImageModel& icon) {
   Item item(command_id, TYPE_HIGHLIGHTED, label);
-  item.icon = gfx::Image(icon);
+  item.icon = icon;
   AppendItem(std::move(item));
 }
 
@@ -198,25 +187,13 @@
   AddSubMenu(command_id, l10n_util::GetStringUTF16(string_id), model);
 }
 
-void SimpleMenuModel::AddSubMenuWithStringIdAndIcon(
-    int command_id,
-    int string_id,
-    MenuModel* model,
-    const gfx::ImageSkia& icon) {
+void SimpleMenuModel::AddSubMenuWithStringIdAndIcon(int command_id,
+                                                    int string_id,
+                                                    MenuModel* model,
+                                                    const ImageModel& icon) {
   Item item(command_id, TYPE_SUBMENU, l10n_util::GetStringUTF16(string_id));
   item.submenu = model;
-  item.icon = gfx::Image(icon);
-  AppendItem(std::move(item));
-}
-
-void SimpleMenuModel::AddSubMenuWithStringIdAndIcon(
-    int command_id,
-    int string_id,
-    MenuModel* model,
-    const gfx::VectorIcon& icon) {
-  Item item(command_id, TYPE_SUBMENU, l10n_util::GetStringUTF16(string_id));
-  item.submenu = model;
-  item.vector_icon = &icon;
+  item.icon = icon;
   AppendItem(std::move(item));
 }
 
@@ -232,23 +209,11 @@
     int command_id,
     int string_id,
     MenuModel* model,
-    const gfx::ImageSkia& icon) {
+    const ImageModel& icon) {
   Item item(command_id, TYPE_ACTIONABLE_SUBMENU,
             l10n_util::GetStringUTF16(string_id));
   item.submenu = model;
-  item.icon = gfx::Image(icon);
-  AppendItem(std::move(item));
-}
-
-void SimpleMenuModel::AddActionableSubmenuWithStringIdAndIcon(
-    int command_id,
-    int string_id,
-    MenuModel* model,
-    const gfx::VectorIcon& icon) {
-  Item item(command_id, TYPE_ACTIONABLE_SUBMENU,
-            l10n_util::GetStringUTF16(string_id));
-  item.submenu = model;
-  item.vector_icon = &icon;
+  item.icon = icon;
   AppendItem(std::move(item));
 }
 
@@ -321,17 +286,10 @@
   MenuItemsChanged();
 }
 
-void SimpleMenuModel::SetIcon(int index, const gfx::Image& icon) {
-  Item* item = &items_[ValidateItemIndex(index)];
-  DCHECK(!item->vector_icon);
-  item->icon = icon;
-  MenuItemsChanged();
-}
-
-void SimpleMenuModel::SetIcon(int index, const gfx::VectorIcon& icon) {
+void SimpleMenuModel::SetIcon(int index, const ui::ImageModel& icon) {
   Item* item = &items_[ValidateItemIndex(index)];
   DCHECK(item->icon.IsEmpty());
-  item->vector_icon = &icon;
+  item->icon = icon;
   MenuItemsChanged();
 }
 
@@ -346,8 +304,8 @@
 }
 
 void SimpleMenuModel::SetMinorIcon(int index,
-                                   const gfx::VectorIcon& minor_icon) {
-  items_[ValidateItemIndex(index)].minor_icon = &minor_icon;
+                                   const ui::ImageModel& minor_icon) {
+  items_[ValidateItemIndex(index)].minor_icon = minor_icon;
 }
 
 void SimpleMenuModel::SetEnabledAt(int index, bool enabled) {
@@ -384,8 +342,7 @@
 
 bool SimpleMenuModel::HasIcons() const {
   for (int i = 0; i < GetItemCount(); ++i) {
-    gfx::Image icon;
-    if (GetIconAt(i, &icon) || GetVectorIconAt(i))
+    if (!GetIconAt(i).IsEmpty())
       return true;
   }
 
@@ -418,7 +375,7 @@
   return items_[ValidateItemIndex(index)].minor_text;
 }
 
-const gfx::VectorIcon* SimpleMenuModel::GetMinorIconAt(int index) const {
+ImageModel SimpleMenuModel::GetMinorIconAt(int index) const {
   return items_[ValidateItemIndex(index)].minor_icon;
 }
 
@@ -449,23 +406,16 @@
   return items_[ValidateItemIndex(index)].group_id;
 }
 
-bool SimpleMenuModel::GetIconAt(int index, gfx::Image* icon) const {
-  if (IsItemDynamicAt(index))
-    return delegate_->GetIconForCommandId(GetCommandIdAt(index), icon);
+ImageModel SimpleMenuModel::GetIconAt(int index) const {
+  if (IsItemDynamicAt(index)) {
+    gfx::Image icon;
+    if (delegate_->GetIconForCommandId(GetCommandIdAt(index), &icon))
+      return ImageModel::FromImage(icon);
+    return ImageModel();
+  }
 
   ValidateItemIndex(index);
-  if (items_[index].icon.IsEmpty())
-    return false;
-
-  *icon = items_[index].icon;
-  return true;
-}
-
-const gfx::VectorIcon* SimpleMenuModel::GetVectorIconAt(int index) const {
-  if (IsItemDynamicAt(index))
-    return delegate_->GetVectorIconForCommandId(GetCommandIdAt(index));
-
-  return items_[ValidateItemIndex(index)].vector_icon;
+  return items_[index].icon;
 }
 
 ButtonMenuItemModel* SimpleMenuModel::GetButtonMenuItemAt(int index) const {
diff --git a/ui/base/models/simple_menu_model.h b/ui/base/models/simple_menu_model.h
index 1ae8909..a6ae5050 100644
--- a/ui/base/models/simple_menu_model.h
+++ b/ui/base/models/simple_menu_model.h
@@ -13,10 +13,11 @@
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string16.h"
 #include "ui/base/accelerators/accelerator.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/models/menu_model.h"
-#include "ui/gfx/image/image.h"
 
 namespace gfx {
+class Image;
 struct VectorIcon;
 }
 
@@ -88,23 +89,17 @@
   void AddItemWithStringId(int command_id, int string_id);
   void AddItemWithIcon(int command_id,
                        const base::string16& label,
-                       const gfx::ImageSkia& icon);
-  void AddItemWithIcon(int command_id,
-                       const base::string16& label,
-                       const gfx::VectorIcon& icon);
+                       const ui::ImageModel& icon);
   void AddItemWithStringIdAndIcon(int command_id,
                                   int string_id,
-                                  const gfx::ImageSkia& icon);
-  void AddItemWithStringIdAndIcon(int command_id,
-                                  int string_id,
-                                  const gfx::VectorIcon& icon);
+                                  const ui::ImageModel& icon);
   void AddCheckItem(int command_id, const base::string16& label);
   void AddCheckItemWithStringId(int command_id, int string_id);
   void AddRadioItem(int command_id, const base::string16& label, int group_id);
   void AddRadioItemWithStringId(int command_id, int string_id, int group_id);
   void AddHighlightedItemWithIcon(int command_id,
                                   const base::string16& label,
-                                  const gfx::ImageSkia& icon);
+                                  const ui::ImageModel& icon);
   void AddTitle(const base::string16& label);
 
   // Adds a separator of the specified type to the model.
@@ -124,22 +119,14 @@
   void AddSubMenuWithStringIdAndIcon(int command_id,
                                      int string_id,
                                      MenuModel* model,
-                                     const gfx::ImageSkia& icon);
-  void AddSubMenuWithStringIdAndIcon(int command_id,
-                                     int string_id,
-                                     MenuModel* model,
-                                     const gfx::VectorIcon& icon);
+                                     const ui::ImageModel& icon);
   void AddActionableSubMenu(int command_id,
                             const base::string16& label,
                             MenuModel* model);
   void AddActionableSubmenuWithStringIdAndIcon(int command_id,
                                                int string_id,
                                                MenuModel* model,
-                                               const gfx::ImageSkia& icon);
-  void AddActionableSubmenuWithStringIdAndIcon(int command_id,
-                                               int string_id,
-                                               MenuModel* model,
-                                               const gfx::VectorIcon& icon);
+                                               const ui::ImageModel& icon);
 
   // Methods for inserting items into the model.
   void InsertItemAt(int index, int command_id, const base::string16& label);
@@ -166,10 +153,7 @@
   void RemoveItemAt(int index);
 
   // Sets the icon for the item at |index|.
-  void SetIcon(int index, const gfx::Image& icon);
-
-  // As above, but uses a VectorIcon. Only one of the two should be set.
-  void SetIcon(int index, const gfx::VectorIcon& icon);
+  void SetIcon(int index, const ui::ImageModel& icon);
 
   // Sets the label for the item at |index|.
   void SetLabel(int index, const base::string16& label);
@@ -178,7 +162,7 @@
   void SetMinorText(int index, const base::string16& minor_text);
 
   // Sets the minor icon for the item at |index|.
-  void SetMinorIcon(int index, const gfx::VectorIcon& minor_icon);
+  void SetMinorIcon(int index, const ui::ImageModel& minor_icon);
 
   // Sets whether the item at |index| is enabled.
   void SetEnabledAt(int index, bool enabled);
@@ -201,13 +185,12 @@
   int GetCommandIdAt(int index) const override;
   base::string16 GetLabelAt(int index) const override;
   base::string16 GetMinorTextAt(int index) const override;
-  const gfx::VectorIcon* GetMinorIconAt(int index) const override;
+  ImageModel GetMinorIconAt(int index) const override;
   bool IsItemDynamicAt(int index) const override;
   bool GetAcceleratorAt(int index, ui::Accelerator* accelerator) const override;
   bool IsItemCheckedAt(int index) const override;
   int GetGroupIdAt(int index) const override;
-  bool GetIconAt(int index, gfx::Image* icon) const override;
-  const gfx::VectorIcon* GetVectorIconAt(int index) const override;
+  ImageModel GetIconAt(int index) const override;
   ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override;
   bool IsEnabledAt(int index) const override;
   bool IsVisibleAt(int index) const override;
@@ -236,9 +219,8 @@
     ItemType type = TYPE_COMMAND;
     base::string16 label;
     base::string16 minor_text;
-    const gfx::VectorIcon* minor_icon = nullptr;
-    gfx::Image icon;
-    const gfx::VectorIcon* vector_icon = nullptr;
+    ImageModel minor_icon;
+    ImageModel icon;
     int group_id = -1;
     MenuModel* submenu = nullptr;
     ButtonMenuItemModel* button_model = nullptr;
diff --git a/ui/base/models/simple_menu_model_unittest.cc b/ui/base/models/simple_menu_model_unittest.cc
index a0365419..56b40a7b 100644
--- a/ui/base/models/simple_menu_model_unittest.cc
+++ b/ui/base/models/simple_menu_model_unittest.cc
@@ -44,7 +44,11 @@
   }
 
   bool GetIconForCommandId(int command_id, gfx::Image* icon) const override {
-    return item_with_icon_ == command_id;
+    if (item_with_icon_ == command_id) {
+      *icon = gfx::test::CreateImage(16, 16);
+      return true;
+    }
+    return false;
   }
 
  private:
@@ -164,7 +168,7 @@
 
   simple_menu_model.AddItemWithIcon(
       /*command_id*/ 11, base::ASCIIToUTF16("menu item"),
-      gfx::test::CreateImage(16, 16).AsImageSkia());
+      ui::ImageModel::FromImage(gfx::test::CreateImage(16, 16)));
   EXPECT_TRUE(simple_menu_model.HasIcons());
 }
 
@@ -179,7 +183,8 @@
   gfx::VectorIcon circle_icon = {rep, 1, "circle"};
 
   simple_menu_model.AddItemWithIcon(
-      /*command_id*/ 11, base::ASCIIToUTF16("menu item"), circle_icon);
+      /*command_id*/ 11, base::ASCIIToUTF16("menu item"),
+      ui::ImageModel::FromVectorIcon(circle_icon));
   EXPECT_TRUE(simple_menu_model.HasIcons());
 }
 
diff --git a/ui/compositor/BUILD.gn b/ui/compositor/BUILD.gn
index 2cf0ce0..50c7595 100644
--- a/ui/compositor/BUILD.gn
+++ b/ui/compositor/BUILD.gn
@@ -76,6 +76,9 @@
     "scoped_animation_duration_scale_mode.h",
     "scoped_layer_animation_settings.cc",
     "scoped_layer_animation_settings.h",
+    "throughput_tracker.cc",
+    "throughput_tracker.h",
+    "throughput_tracker_host.h",
     "transform_animation_curve_adapter.cc",
     "transform_animation_curve_adapter.h",
     "transform_recorder.cc",
diff --git a/ui/compositor/compositor.cc b/ui/compositor/compositor.cc
index e94d022..e66281a 100644
--- a/ui/compositor/compositor.cc
+++ b/ui/compositor/compositor.cc
@@ -695,6 +695,25 @@
   NOTREACHED();
 }
 
+void Compositor::StartThroughputTracker(
+    TrackerId tracker_id,
+    ThroughputTrackerHost::ReportCallback callback) {
+  DCHECK(!base::Contains(throughput_tracker_map_, tracker_id));
+  throughput_tracker_map_[tracker_id] = std::move(callback);
+  animation_host_->StartThroughputTracking(tracker_id);
+}
+
+void Compositor::StopThroughtputTracker(TrackerId tracker_id) {
+  DCHECK(base::Contains(throughput_tracker_map_, tracker_id));
+  animation_host_->StopThroughputTracking(tracker_id);
+}
+
+void Compositor::CancelThroughtputTracker(TrackerId tracker_id) {
+  DCHECK(base::Contains(throughput_tracker_map_, tracker_id));
+  StopThroughtputTracker(tracker_id);
+  throughput_tracker_map_.erase(tracker_id);
+}
+
 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
 void Compositor::OnCompleteSwapWithNewSize(const gfx::Size& size) {
   for (auto& observer : observer_list_)
@@ -722,4 +741,8 @@
   host_->RequestPresentationTimeForNextFrame(std::move(callback));
 }
 
+ThroughputTracker Compositor::RequestNewThroughputTracker() {
+  return ThroughputTracker(next_throughput_tracker_id_++, this);
+}
+
 }  // namespace ui
diff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h
index 3e2d230..601c3d1 100644
--- a/ui/compositor/compositor.h
+++ b/ui/compositor/compositor.h
@@ -11,6 +11,7 @@
 #include <string>
 
 #include "base/callback_forward.h"
+#include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
@@ -33,6 +34,8 @@
 #include "ui/compositor/compositor_lock.h"
 #include "ui/compositor/compositor_observer.h"
 #include "ui/compositor/layer_animator_collection.h"
+#include "ui/compositor/throughput_tracker.h"
+#include "ui/compositor/throughput_tracker_host.h"
 #include "ui/gfx/display_color_spaces.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/geometry/vector2d.h"
@@ -80,6 +83,7 @@
 class Layer;
 class ScopedAnimationDurationScaleMode;
 class ScrollInputHandler;
+class ThroughputTracker;
 struct PendingBeginFrameArgs;
 
 constexpr int kCompositorLockTimeoutMs = 67;
@@ -129,7 +133,8 @@
 // view hierarchy.
 class COMPOSITOR_EXPORT Compositor : public cc::LayerTreeHostClient,
                                      public cc::LayerTreeHostSingleThreadClient,
-                                     public viz::HostFrameSinkClient {
+                                     public viz::HostFrameSinkClient,
+                                     public ThroughputTrackerHost {
  public:
   Compositor(const viz::FrameSinkId& frame_sink_id,
              ui::ContextFactory* context_factory,
@@ -295,6 +300,9 @@
       bool force,
       base::OnceCallback<void(const viz::BeginFrameAck&)> callback);
 
+  // Creates a ThroughputTracker for tracking this Compositor.
+  ThroughputTracker RequestNewThroughputTracker();
+
   // LayerTreeHostClient implementation.
   void WillBeginMainFrame() override {}
   void DidBeginMainFrame() override {}
@@ -341,6 +349,13 @@
   void OnFirstSurfaceActivation(const viz::SurfaceInfo& surface_info) override;
   void OnFrameTokenChanged(uint32_t frame_token) override;
 
+  // ThroughputTrackerHost implementation.
+  void StartThroughputTracker(
+      TrackerId tracker_id,
+      ThroughputTrackerHost::ReportCallback callback) override;
+  void StopThroughtputTracker(TrackerId tracker_id) override;
+  void CancelThroughtputTracker(TrackerId tracker_id) override;
+
 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
   void OnCompleteSwapWithNewSize(const gfx::Size& size);
 #endif
@@ -446,6 +461,11 @@
   // Set in DisableSwapUntilResize and reset when a resize happens.
   bool disabled_swap_until_resize_ = false;
 
+  TrackerId next_throughput_tracker_id_ = 1u;
+  using ThroughputTrackerMap =
+      base::flat_map<TrackerId, ThroughputTrackerHost::ReportCallback>;
+  ThroughputTrackerMap throughput_tracker_map_;
+
   base::WeakPtrFactory<Compositor> context_creation_weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(Compositor);
diff --git a/ui/compositor/compositor_unittest.cc b/ui/compositor/compositor_unittest.cc
index 0eb30ab..4e3fd13 100644
--- a/ui/compositor/compositor_unittest.cc
+++ b/ui/compositor/compositor_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
+#include "base/test/bind_test_util.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_mock_time_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -28,8 +29,8 @@
 
 class CompositorTest : public testing::Test {
  public:
-  CompositorTest() {}
-  ~CompositorTest() override {}
+  CompositorTest() = default;
+  ~CompositorTest() override = default;
 
   void SetUp() override {
     context_factories_ = std::make_unique<TestContextFactories>(false);
@@ -164,6 +165,71 @@
   compositor()->SetVisible(true);
 }
 
+TEST_F(CompositorTestWithMessageLoop, MoveThroughputTracker) {
+  // Move a not started instance.
+  {
+    auto tracker = compositor()->RequestNewThroughputTracker();
+    auto moved_tracker = std::move(tracker);
+  }
+
+  // Move a started instance.
+  {
+    auto tracker = compositor()->RequestNewThroughputTracker();
+    tracker.Start(base::BindLambdaForTesting(
+        [&](cc::FrameSequenceMetrics::ThroughputData throughput) {
+          // This should not be called since the tracking is auto canceled.
+          ADD_FAILURE();
+        }));
+    auto moved_tracker = std::move(tracker);
+  }
+
+  // Move a started instance and stop.
+  {
+    auto tracker = compositor()->RequestNewThroughputTracker();
+    tracker.Start(base::BindLambdaForTesting(
+        [&](cc::FrameSequenceMetrics::ThroughputData throughput) {
+          // May be called since Stop() is called.
+        }));
+    auto moved_tracker = std::move(tracker);
+    moved_tracker.Stop();
+  }
+
+  // Move a started instance and cancel.
+  {
+    auto tracker = compositor()->RequestNewThroughputTracker();
+    tracker.Start(base::BindLambdaForTesting(
+        [&](cc::FrameSequenceMetrics::ThroughputData throughput) {
+          // This should not be called since Cancel() is called.
+          ADD_FAILURE();
+        }));
+    auto moved_tracker = std::move(tracker);
+    moved_tracker.Cancel();
+  }
+
+  // Move a stopped instance.
+  {
+    auto tracker = compositor()->RequestNewThroughputTracker();
+    tracker.Start(base::BindLambdaForTesting(
+        [&](cc::FrameSequenceMetrics::ThroughputData throughput) {
+          // May be called since Stop() is called.
+        }));
+    tracker.Stop();
+    auto moved_tracker = std::move(tracker);
+  }
+
+  // Move a canceled instance.
+  {
+    auto tracker = compositor()->RequestNewThroughputTracker();
+    tracker.Start(base::BindLambdaForTesting(
+        [&](cc::FrameSequenceMetrics::ThroughputData throughput) {
+          // This should not be called since Cancel() is called.
+          ADD_FAILURE();
+        }));
+    tracker.Cancel();
+    auto moved_tracker = std::move(tracker);
+  }
+}
+
 #if defined(OS_WIN)
 // TODO(crbug.com/608436): Flaky on windows trybots
 #define MAYBE_CreateAndReleaseOutputSurface \
diff --git a/ui/compositor/throughput_tracker.cc b/ui/compositor/throughput_tracker.cc
new file mode 100644
index 0000000..9b5aed1
--- /dev/null
+++ b/ui/compositor/throughput_tracker.cc
@@ -0,0 +1,54 @@
+// 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 "ui/compositor/throughput_tracker.h"
+
+#include <utility>
+
+#include "base/callback.h"
+#include "base/logging.h"
+
+namespace ui {
+
+ThroughputTracker::ThroughputTracker(TrackerId id, ThroughputTrackerHost* host)
+    : id_(id), host_(host) {
+  DCHECK(host_);
+}
+
+ThroughputTracker::ThroughputTracker(ThroughputTracker&& other) {
+  *this = std::move(other);
+}
+
+ThroughputTracker& ThroughputTracker::operator=(ThroughputTracker&& other) {
+  id_ = other.id_;
+  host_ = other.host_;
+  started_ = other.started_;
+
+  other.id_ = kInvalidId;
+  other.host_ = nullptr;
+  other.started_ = false;
+  return *this;
+}
+
+ThroughputTracker::~ThroughputTracker() {
+  if (started_)
+    Cancel();
+}
+
+void ThroughputTracker::Start(ThroughputTrackerHost::ReportCallback callback) {
+  started_ = true;
+  host_->StartThroughputTracker(id_, std::move(callback));
+}
+
+void ThroughputTracker::Stop() {
+  started_ = false;
+  host_->StopThroughtputTracker(id_);
+}
+
+void ThroughputTracker::Cancel() {
+  started_ = false;
+  host_->CancelThroughtputTracker(id_);
+}
+
+}  // namespace ui
diff --git a/ui/compositor/throughput_tracker.h b/ui/compositor/throughput_tracker.h
new file mode 100644
index 0000000..def502b
--- /dev/null
+++ b/ui/compositor/throughput_tracker.h
@@ -0,0 +1,60 @@
+// 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 UI_COMPOSITOR_THROUGHPUT_TRACKER_H_
+#define UI_COMPOSITOR_THROUGHPUT_TRACKER_H_
+
+#include "base/callback_forward.h"
+#include "ui/compositor/compositor_export.h"
+#include "ui/compositor/throughput_tracker_host.h"
+
+namespace ui {
+
+class Compositor;
+class ThroughputTrackerHost;
+
+// A class to track the throughput of Compositor. The tracking is identified by
+// an id. The id is passed into impl side and be used as the sequence id to
+// create and stop a kCustom typed cc::FrameSequenceTracker. The class is
+// move-only to have only one holder of the id. When ThroughputTracker is
+// destroyed with an active tracking, the tracking will be canceled and report
+// callback will not be invoked.
+class COMPOSITOR_EXPORT ThroughputTracker {
+ public:
+  using TrackerId = ThroughputTrackerHost::TrackerId;
+
+  // Move only.
+  ThroughputTracker(ThroughputTracker&& other);
+  ThroughputTracker& operator=(ThroughputTracker&& other);
+
+  ~ThroughputTracker();
+
+  // Starts tracking Compositor and provides a callback for reporting. The
+  // throughput data collection starts after the next commit.
+  void Start(ThroughputTrackerHost::ReportCallback callback);
+
+  // Stops tracking. The supplied callback will be invoked when the data
+  // collection finishes after the next frame presentation. Note that no data
+  // will be reported if Stop() is not called,
+  void Stop();
+
+  // Cancels tracking. The supplied callback will not be invoked.
+  void Cancel();
+
+ private:
+  friend class Compositor;
+
+  // Private since it should only be created via Compositor's
+  // RequestNewThroughputTracker call.
+  ThroughputTracker(TrackerId id, ThroughputTrackerHost* host);
+
+  static const TrackerId kInvalidId = 0u;
+  TrackerId id_ = kInvalidId;
+  ThroughputTrackerHost* host_ = nullptr;
+  bool started_ = false;
+};
+
+}  // namespace ui
+
+#endif  // UI_COMPOSITOR_THROUGHPUT_TRACKER_H_
diff --git a/ui/compositor/throughput_tracker_host.h b/ui/compositor/throughput_tracker_host.h
new file mode 100644
index 0000000..9082d00
--- /dev/null
+++ b/ui/compositor/throughput_tracker_host.h
@@ -0,0 +1,37 @@
+// 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 UI_COMPOSITOR_THROUGHPUT_TRACKER_HOST_H_
+#define UI_COMPOSITOR_THROUGHPUT_TRACKER_HOST_H_
+
+#include "base/callback_forward.h"
+#include "cc/metrics/frame_sequence_tracker.h"
+#include "ui/compositor/compositor_export.h"
+
+namespace ui {
+
+// An interface for ThroughputTracker to call its host.
+class COMPOSITOR_EXPORT ThroughputTrackerHost {
+ public:
+  using TrackerId = size_t;
+
+  virtual ~ThroughputTrackerHost() = default;
+
+  // Starts the tracking for the given tracker id. |callback| is invoked after
+  // the tracker is stopped and the throughput data is collected.
+  using ReportCallback = base::OnceCallback<void(
+      const cc::FrameSequenceMetrics::ThroughputData throughput)>;
+  virtual void StartThroughputTracker(TrackerId tracker_id,
+                                      ReportCallback callback) = 0;
+
+  // Stops the tracking for the given tracker id.
+  virtual void StopThroughtputTracker(TrackerId tracker_id) = 0;
+
+  // Cancels the tracking for the given tracker id.
+  virtual void CancelThroughtputTracker(TrackerId tracker_id) = 0;
+};
+
+}  // namespace ui
+
+#endif  // UI_COMPOSITOR_THROUGHPUT_TRACKER_HOST_H_
diff --git a/ui/display/manager/managed_display_info.cc b/ui/display/manager/managed_display_info.cc
index 0cfb060..e40b18c 100644
--- a/ui/display/manager/managed_display_info.cc
+++ b/ui/display/manager/managed_display_info.cc
@@ -19,6 +19,7 @@
 #include "ui/display/display.h"
 #include "ui/display/display_features.h"
 #include "ui/display/display_switches.h"
+#include "ui/display/manager/display_manager_utilities.h"
 #include "ui/gfx/geometry/size_conversions.h"
 #include "ui/gfx/geometry/size_f.h"
 
@@ -33,7 +34,7 @@
 // Use larger than max int to catch overflow early.
 const int64_t kSynthesizedDisplayIdStart = 2200000000LL;
 
-int64_t synthesized_display_id = kSynthesizedDisplayIdStart;
+int64_t next_synthesized_display_id = kSynthesizedDisplayIdStart;
 
 const float kDpi96 = 96.0;
 
@@ -234,8 +235,10 @@
                            true, dm.device_scale_factor());
   }
 
-  if (id == kInvalidDisplayId)
-    id = synthesized_display_id++;
+  if (id == kInvalidDisplayId) {
+    id = next_synthesized_display_id;
+    next_synthesized_display_id = GetNextSynthesizedDisplayId(id);
+  }
   ManagedDisplayInfo display_info(
       id, base::StringPrintf("Display-%d", static_cast<int>(id)), has_overscan);
   display_info.set_device_scale_factor(device_scale_factor);
@@ -489,7 +492,17 @@
 }
 
 void ResetDisplayIdForTest() {
-  synthesized_display_id = kSynthesizedDisplayIdStart;
+  next_synthesized_display_id = kSynthesizedDisplayIdStart;
+}
+
+int64_t GetNextSynthesizedDisplayId(int64_t id) {
+  int next_output_index = id & 0xFF;
+  next_output_index++;
+  DCHECK_GT(0x100, next_output_index);
+  int64_t base = GetDisplayIdWithoutOutputIndex(id);
+  if (id == kSynthesizedDisplayIdStart)
+    return id + 0x100 + next_output_index;
+  return base + next_output_index;
 }
 
 }  // namespace display
diff --git a/ui/display/manager/managed_display_info.h b/ui/display/manager/managed_display_info.h
index 263a4f6..e340223f 100644
--- a/ui/display/manager/managed_display_info.h
+++ b/ui/display/manager/managed_display_info.h
@@ -383,6 +383,10 @@
 // is necessary to avoid overflowing the output index.
 void DISPLAY_MANAGER_EXPORT ResetDisplayIdForTest();
 
+// Generates a fake, synthesized display ID that will be used when the
+// |kInvalidDisplayId| is passed to |ManagedDisplayInfo| constructor.
+int64_t DISPLAY_MANAGER_EXPORT GetNextSynthesizedDisplayId(int64_t id);
+
 }  // namespace display
 
 #endif  //  UI_DISPLAY_MANAGER_MANAGED_DISPLAY_INFO_H_
diff --git a/ui/display/test/display_manager_test_api.cc b/ui/display/test/display_manager_test_api.cc
index e89e10d..91f4c93 100644
--- a/ui/display/test/display_manager_test_api.cc
+++ b/ui/display/test/display_manager_test_api.cc
@@ -218,15 +218,17 @@
   return list;
 }
 
-DisplayIdList CreateDisplayIdListN(size_t count, ...) {
+DisplayIdList CreateDisplayIdListN(int64_t start_id, size_t count) {
   DisplayIdList list;
-  va_list args;
-  va_start(args, count);
-  for (size_t i = 0; i < count; i++) {
-    int64_t id = va_arg(args, int64_t);
+  list.push_back(start_id);
+  int64_t id = start_id;
+  size_t N = count;
+  while (count-- > 1) {
+    id = display::GetNextSynthesizedDisplayId(id);
     list.push_back(id);
   }
   SortDisplayIdList(&list);
+  DCHECK_EQ(N, list.size());
   return list;
 }
 
diff --git a/ui/display/test/display_manager_test_api.h b/ui/display/test/display_manager_test_api.h
index 8b8b44e..bfffa1c 100644
--- a/ui/display/test/display_manager_test_api.h
+++ b/ui/display/test/display_manager_test_api.h
@@ -101,7 +101,9 @@
 
 // Creates the DisplayIdList from ints.
 DISPLAY_EXPORT DisplayIdList CreateDisplayIdList2(int64_t id1, int64_t id2);
-DISPLAY_EXPORT DisplayIdList CreateDisplayIdListN(size_t count, ...);
+
+// Create the N number of DisplayIdList starting from |start_id},
+DISPLAY_EXPORT DisplayIdList CreateDisplayIdListN(int64_t start_id, size_t N);
 
 }  // namespace test
 }  // namespace display
diff --git a/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn b/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
index 14e72e2..69e5e1e 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
@@ -123,9 +123,14 @@
 }
 
 js_library("breadcrumb") {
+  deps = [ "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu" ]
+}
+
+js_unittest("breadcrumb_unittest") {
   deps = [
-    "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu",
+    ":breadcrumb",
     "//ui/webui/resources/js:assert",
+    "//ui/webui/resources/js:webui_resource_test",
   ]
 }
 
@@ -506,6 +511,7 @@
 js_test_gen_html("js_test_gen_html") {
   deps = [
     ":actions_submenu_unittest",
+    ":breadcrumb_unittest",
     ":directory_tree_unittest",
     ":file_list_selection_model_unittest",
     ":file_manager_dialog_base_unittest",
diff --git a/ui/file_manager/file_manager/foreground/js/ui/breadcrumb.js b/ui/file_manager/file_manager/foreground/js/ui/breadcrumb.js
index 06e0ae9..3b9bb02 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/breadcrumb.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/breadcrumb.js
@@ -311,8 +311,9 @@
    * its parts, which are stored in the <button>.textContent.
    *
    * @return {!Array<HTMLButtonElement>}
+   * @private
    */
-  getBreadcrumbButtons() {
+  getBreadcrumbButtons_() {
     const parts = this.shadowRoot.querySelectorAll('button[id]:not([hidden])');
     if (this.parts_.length <= 4) {
       return Array.from(parts);
@@ -332,7 +333,7 @@
    *    attribute on the returned buttons.
    */
   getEllipsisButtons() {
-    return this.getBreadcrumbButtons().filter(button => {
+    return this.getBreadcrumbButtons_().filter(button => {
       if (!button.hasAttribute('has-tooltip') && button.offsetWidth) {
         return button.offsetWidth < button.scrollWidth;
       }
@@ -375,7 +376,7 @@
     }
 
     if (element instanceof HTMLButtonElement) {
-      const parts = this.getBreadcrumbButtons();
+      const parts = this.getBreadcrumbButtons_();
       this.signal_(parts.indexOf(element));
     }
   }
diff --git a/ui/file_manager/file_manager/foreground/js/ui/breadcrumb_unittest.js b/ui/file_manager/file_manager/foreground/js/ui/breadcrumb_unittest.js
new file mode 100644
index 0000000..f48b9ed
--- /dev/null
+++ b/ui/file_manager/file_manager/foreground/js/ui/breadcrumb_unittest.js
@@ -0,0 +1,818 @@
+// 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.
+
+'use strict';
+
+/** @const {boolean} */
+window.UNIT_TEST = true;
+
+/**
+ * Creates new <bread-drumb> element for each test. Asserts it has no initial
+ * path using the element.path getter.
+ */
+function setUp() {
+  document.body.innerHTML = '<bread-crumb></bread-crumb>';
+  const path = assert(document.querySelector('bread-crumb')).path;
+  assertEquals('', path);
+}
+
+/**
+ * Returns the <bread-crumb> element.
+ * @return {!BreadCrumb|!Element}
+ */
+function getBreadCrumb() {
+  const element = assert(document.querySelector('bread-crumb'));
+  assertNotEqual('none', window.getComputedStyle(element).display);
+  assertFalse(element.hasAttribute('hidden'));
+  return element;
+}
+
+/**
+ * Returns the <bread-crumb> child button elements. There are 4 main buttons
+ * and one elider button (so at least 5) plus optional drop-down menu buttons.
+ * @return {!Array<!HTMLButtonElement>}
+ */
+function getAllBreadCrumbButtons() {
+  const buttons = getBreadCrumb().shadowRoot.querySelectorAll('button');
+  assert(buttons) && assert(buttons.length >= 5, 'too few buttons');
+  return Array.from(buttons);
+}
+
+/**
+ * Returns the not-hidden <bread-crumb> main button elements. The breadcrumb
+ * main buttons have an id, all other breadcrumb buttons do not.
+ * @return {!Array<HTMLButtonElement>}
+ */
+function getVisibleBreadCrumbMainButtons() {
+  const notHiddenMain = 'button[id]:not([hidden])';
+  const buttons = getBreadCrumb().shadowRoot.querySelectorAll(notHiddenMain);
+  return Array.from(buttons);
+}
+
+/**
+ * Returns the last not-hidden <bread-crumb> main button element.
+ * @return {HTMLButtonElement}
+ */
+function getLastVisibleBreadCrumbMainButton() {
+  return getVisibleBreadCrumbMainButtons().pop();
+}
+
+/**
+ * Returns the <bread-crumb> elider button element.
+ * @return {!HTMLButtonElement}
+ */
+function getBreadCrumbEliderButton() {
+  const elider = 'button[elider]';
+  const button = getBreadCrumb().shadowRoot.querySelectorAll(elider);
+  assert(button) && assert(button.length === 1, 'invalid elider button');
+  return button[0];
+}
+
+/**
+ * Returns the <bread-crumb> drop-down menu button elements.
+ * @return {!Array<!HTMLButtonElement>}
+ */
+function getBreadCrumbMenuButtons() {
+  const menuButton = 'cr-action-menu button';
+  const buttons = getBreadCrumb().shadowRoot.querySelectorAll(menuButton);
+  return Array.from(assert(buttons, 'no menu buttons'));
+}
+
+/**
+ * Returns <bread-crumb> main button visual state.
+ * @param {!HTMLButtonElement} button Main button (these have an id).
+ * @param {number} i Number to assign to the button.
+ * @return {string}
+ */
+function getMainButtonState(button, i) {
+  const display = window.getComputedStyle(button).display;
+
+  let result = i + ': display:' + display + ' id=' + button.id;
+  if (!button.hasAttribute('hidden')) {
+    result += ' text=[' + button.textContent + ']';
+  } else {
+    assertEquals('none', display);
+    result += ' hidden';
+  }
+
+  assert(button.id, 'main buttons should have an id');
+  return result;
+}
+
+/**
+ * Returns <bread-crumb> elider button visual state.
+ * @param {!HTMLButtonElement} button Elider button.
+ * @param {number} i Number to assign to the button.
+ * @return {string}
+ */
+function getEliderButtonState(button, i) {
+  const display = window.getComputedStyle(button).display;
+
+  const result = i + ': display:' + display;
+  const attributes = [];
+  for (const value of button.getAttributeNames().values()) {
+    if (value === 'aria-expanded') {  // drop-down menu: opened || closed
+      attributes.push(value + '=' + button.getAttribute('aria-expanded'));
+    } else if (value === 'hidden') {
+      assertEquals('none', display);
+      attributes.push(value);
+    } else if (value !== 'elider') {
+      attributes.push(value);
+    }
+  }
+
+  assertFalse(!!button.id, 'elider button should not have an id');
+  assert(button.hasAttribute('elider'));
+  return result + ' elider[' + attributes.sort() + ']';
+}
+
+/**
+ * Returns <bread-crumb> drop-down menu button visual state.
+ * @param {!HTMLButtonElement} button Drop-down menu button.
+ * @return {string}
+ */
+function getDropDownMenuButtonState(button) {
+  const display = window.getComputedStyle(button).display;
+
+  let result = `${button.classList.toString()}: display:` + display;
+  if (!button.hasAttribute('hidden')) {
+    result += ' text=[' + button.textContent + ']';
+  } else {
+    assertEquals('none', display);
+    result += ' hidden';
+  }
+
+  assertFalse(!!button.id, 'drop-down buttons should not have an id');
+  assert(button.classList.contains('dropdown-item'));
+  return result;
+}
+
+/**
+ * Returns the <bread-crumb> buttons visual state.
+ * @return {string}
+ */
+function getBreadCrumbButtonState() {
+  const parts = [];
+  const menus = [];
+
+  const buttons = getAllBreadCrumbButtons();
+  let number = 0;
+  buttons.forEach((button) => {
+    if (button.id) {  // Main buttons have an id.
+      parts.push(getMainButtonState(button, ++number));
+    } else if (button.hasAttribute('elider')) {  // Elider button.
+      parts.push(getEliderButtonState(button, ++number));
+    } else {  // A drop-down menu button.
+      menus.push(getDropDownMenuButtonState(button));
+    }
+  });
+
+  // Elider should only display for paths with more than 4 parts.
+  if (!getBreadCrumbEliderButton().hasAttribute('hidden')) {
+    assertTrue(buttons.length > 4);
+  }
+
+  // The 'last' main button displayed should always be [disabled].
+  const last = getLastVisibleBreadCrumbMainButton();
+  if (getBreadCrumb().path !== '') {
+    assert(last.hasAttribute('disabled'));
+  }
+
+  if (menus.length) {
+    return [parts[0], parts[1]].concat(menus, parts.slice(2)).join(' ');
+  }
+
+  return parts.join(' ');
+}
+
+/**
+ * Tests rendering an empty path.
+ */
+function testBreadcrumbEmptyPath() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = '';
+
+  // clang-format off
+  const expect = element.path +
+      ' 1: display:none id=first hidden' +
+      ' 2: display:none elider[aria-expanded=false,aria-haspopup,aria-label,hidden]' +
+      ' 3: display:none id=second hidden' +
+      ' 4: display:none id=third hidden' +
+      ' 5: display:none id=fourth hidden';
+  // clang-format on
+
+  const path = element.parts.join('/');
+  assertEquals(expect, path + ' ' + getBreadCrumbButtonState());
+}
+
+/**
+ * Tests rendering a one element path.
+ */
+function testBreadcrumbOnePartPath() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A';
+
+  // clang-format off
+  const expect = element.path +
+    ' 1: display:block id=first text=[A]' +
+    ' 2: display:none elider[aria-expanded=false,aria-haspopup,aria-label,hidden]' +
+    ' 3: display:none id=second hidden' +
+    ' 4: display:none id=third hidden' +
+    ' 5: display:none id=fourth hidden';
+  // clang-format on
+
+  const path = element.parts.join('/');
+  assertEquals(expect, path + ' ' + getBreadCrumbButtonState());
+}
+
+/**
+ * Tests rendering a two element path.
+ */
+function testBreadcrumbTwoPartPath() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B';
+
+  // clang-format off
+  const expect = element.path +
+    ' 1: display:block id=first text=[A]' +
+    ' 2: display:none elider[aria-expanded=false,aria-haspopup,aria-label,hidden]' +
+    ' 3: display:none id=second hidden' +
+    ' 4: display:none id=third hidden' +
+    ' 5: display:block id=fourth text=[B]';
+  // clang-format on
+
+  const path = element.parts.join('/');
+  assertEquals(expect, path + ' ' + getBreadCrumbButtonState());
+}
+
+/**
+ * Tests rendering a three element path.
+ */
+function testBreadcrumbThreePartPath() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/C';
+
+  // clang-format off
+  const expect = element.path +
+    ' 1: display:block id=first text=[A]' +
+    ' 2: display:none elider[aria-expanded=false,aria-haspopup,aria-label,hidden]' +
+    ' 3: display:none id=second hidden' +
+    ' 4: display:block id=third text=[B]' +
+    ' 5: display:block id=fourth text=[C]';
+  // clang-format on
+
+  const path = element.parts.join('/');
+  assertEquals(expect, path + ' ' + getBreadCrumbButtonState());
+}
+
+/**
+ * Tests rendering a four element path.
+ */
+function testBreadcrumbFourPartPath() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/C/D';
+
+  // clang-format off
+  const expect = element.path +
+    ' 1: display:block id=first text=[A]' +
+    ' 2: display:none elider[aria-expanded=false,aria-haspopup,aria-label,hidden]' +
+    ' 3: display:block id=second text=[B]' +
+    ' 4: display:block id=third text=[C]' +
+    ' 5: display:block id=fourth text=[D]';
+  // clang-format on
+
+  const path = element.parts.join('/');
+  assertEquals(expect, path + ' ' + getBreadCrumbButtonState());
+}
+
+/**
+ * Tests rendering a path of more than four parts. The elider button should be
+ * visible (not hidden and have display).
+ *
+ * The drop-down menu button should contain the elided path parts and can have
+ * display, but are invisible because the elider drop-down menu is closed.
+ */
+function testBreadcrumbMoreThanFourElementPathsElide() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/C/D/E/F';
+
+  // Elider button drop-down menu should be in the 'closed' state.
+  const elider = getBreadCrumbEliderButton();
+  assertEquals('false', elider.getAttribute('aria-expanded'));
+
+  // clang-format off
+  const expect = element.path +
+     ' 1: display:block id=first text=[A]' +
+     ' 2: display:flex elider[aria-expanded=false,aria-haspopup,aria-label]' +
+     ' dropdown-item: display:block text=[B]' +
+     ' dropdown-item: display:block text=[C]' +
+     ' dropdown-item: display:block text=[D]' +
+     ' 3: display:none id=second hidden' +
+     ' 4: display:block id=third text=[E]' +
+     ' 5: display:block id=fourth text=[F]';
+  // clang-format on
+
+  const path = element.parts.join('/');
+  assertEquals(expect, path + ' ' + getBreadCrumbButtonState());
+}
+
+/**
+ * Tests rendering a path of more than four parts. The elider button should be
+ * visible and clicking it should 'open' and 'close' its drop-down menu.
+ */
+function testBreadcrumbElidedPathEliderButtonClicksOpenDropDownMenu() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/C/D/E';
+
+  // Elider button drop-down menu should be in the 'closed' state.
+  const elider = getBreadCrumbEliderButton();
+  assertEquals('false', elider.getAttribute('aria-expanded'));
+
+  // Clicking the elider button should 'open' its drop-down menu.
+  assertFalse(elider.hasAttribute('hidden'));
+  elider.click();
+  assertEquals('true', elider.getAttribute('aria-expanded'));
+
+  // clang-format off
+  const opened = element.path +
+     ' 1: display:block id=first text=[A]' +
+     ' 2: display:flex elider[aria-expanded=true,aria-haspopup,aria-label]' +
+     ' dropdown-item: display:block text=[B]' +
+     ' dropdown-item: display:block text=[C]' +
+     ' 3: display:none id=second hidden' +
+     ' 4: display:block id=third text=[D]' +
+     ' 5: display:block id=fourth text=[E]';
+  // clang-format on
+
+  const path = element.parts.join('/');
+  assertEquals(opened, path + ' ' + getBreadCrumbButtonState());
+
+  // Clicking the elider again should 'close' the drop-down menu.
+  assertFalse(elider.hasAttribute('hidden'));
+  elider.click();
+  assertEquals('false', elider.getAttribute('aria-expanded'));
+
+  // clang-format off
+  const closed = element.path +
+     ' 1: display:block id=first text=[A]' +
+     ' 2: display:flex elider[aria-expanded=false,aria-haspopup,aria-label]' +
+     ' dropdown-item: display:block text=[B]' +
+     ' dropdown-item: display:block text=[C]' +
+     ' 3: display:none id=second hidden' +
+     ' 4: display:block id=third text=[D]' +
+     ' 5: display:block id=fourth text=[E]';
+  // clang-format on
+
+  assertEquals(closed, path + ' ' + getBreadCrumbButtonState());
+}
+
+/**
+ * Tests that clicking on the main buttons emits a signal that indicates which
+ * part of the breadcrumb path was clicked.
+ */
+async function testBreadcrumbMainButtonClicksEmitNumberSignal(done) {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/C/D/E/F';
+
+  // clang-format off
+  const expect = element.path +
+     ' 1: display:block id=first text=[A]' +  // 1st main button
+     ' 2: display:flex elider[aria-expanded=false,aria-haspopup,aria-label]' +
+     ' dropdown-item: display:block text=[B]' +
+     ' dropdown-item: display:block text=[C]' +
+     ' dropdown-item: display:block text=[D]' +
+     ' 3: display:none id=second hidden' +
+     ' 4: display:block id=third text=[E]' +  // 2nd main button
+     ' 5: display:block id=fourth text=[F]';  // 3rd main button
+  // clang-format on
+
+  const path = element.parts.join('/');
+  assertEquals(expect, path + ' ' + getBreadCrumbButtonState());
+
+  // Set the BreadCrumb signals callback.
+  let signal = null;
+  element.setSignalCallback((number) => {
+    assert(typeof number === 'number');
+    signal = number;
+  });
+
+  const buttons = getVisibleBreadCrumbMainButtons();
+  assertEquals(3, buttons.length, 'three main buttons should be visible');
+
+  signal = null;
+  assertEquals('A', buttons[0].textContent);
+  assertFalse(buttons[0].hasAttribute('disabled'));
+  buttons[0].click();
+  assertEquals(element.parts.indexOf('A'), signal);
+
+  signal = null;
+  assertEquals('E', buttons[1].textContent);
+  assertFalse(buttons[1].hasAttribute('disabled'));
+  buttons[1].click();
+  assertEquals(element.parts.indexOf('E'), signal);
+
+  signal = null;
+  assertEquals('F', buttons[2].textContent);
+  assert(buttons[2].hasAttribute('disabled'));
+  buttons[2].click();  // Ignored: the last main button is always disabled.
+  assertEquals(null, signal);
+
+  done();
+}
+
+/**
+ * Tests that clicking on the menu buttons emits a signal that indicates which
+ * part of the breadcrumb path was clicked.
+ */
+async function testBreadcrumbMenuButtonClicksEmitNumberSignal(done) {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/C/D/E';
+
+  // Elider button drop-down menu should be in the 'closed' state.
+  const elider = getBreadCrumbEliderButton();
+  assertEquals('false', elider.getAttribute('aria-expanded'));
+
+  // Clicking the elider button should 'open' its drop-down menu.
+  assertFalse(elider.hasAttribute('hidden'));
+  elider.click();
+  assertEquals('true', elider.getAttribute('aria-expanded'));
+
+  // clang-format off
+  const opened = element.path +
+     ' 1: display:block id=first text=[A]' +
+     ' 2: display:flex elider[aria-expanded=true,aria-haspopup,aria-label]' +
+     ' dropdown-item: display:block text=[B]' +
+     ' dropdown-item: display:block text=[C]' +
+     ' 3: display:none id=second hidden' +
+     ' 4: display:block id=third text=[D]' +
+     ' 5: display:block id=fourth text=[E]';
+  // clang-format on
+
+  const path = element.parts.join('/');
+  assertEquals(opened, path + ' ' + getBreadCrumbButtonState());
+
+  // Set the BreadCrumb signals callback.
+  let signal = null;
+  element.setSignalCallback((number) => {
+    assert(typeof number === 'number');
+    signal = number;
+  });
+
+  const buttons = getBreadCrumbMenuButtons();
+  assertEquals(2, buttons.length, 'there should be two drop-down items');
+
+  signal = null;
+  assertEquals('B', buttons[0].textContent);
+  assertFalse(buttons[0].hasAttribute('disabled'));
+  buttons[0].click();
+  assertEquals(element.parts.indexOf('B'), signal);
+
+  signal = null;
+  assertEquals('C', buttons[1].textContent);
+  assertFalse(buttons[1].hasAttribute('disabled'));
+  buttons[1].click();
+  assertEquals(element.parts.indexOf('C'), signal);
+
+  done();
+}
+
+/**
+ * Tests that setting the path emits a signal when the rendering of the new
+ * path begins, and when it ends.
+ */
+async function testBreadcrumbSetPathEmitsRenderSignals(done) {
+  const element = getBreadCrumb();
+
+  // Set the BreadCrumb signals callback.
+  const signal = [];
+  element.setSignalCallback((rendered) => {
+    assert(typeof rendered === 'string');
+    signal.push(rendered);
+  });
+
+  // Set path.
+  element.path = 'A/B/C/D/E';
+
+  // Begin: element.path has changed, the buttons have not changed.
+  assertEquals(signal[0], 'path-updated');
+  // End: the element path has been rendered into the main buttons.
+  assertEquals(signal[1], 'path-rendered');
+  assertEquals(2, signal.length);
+
+  done();
+}
+
+/**
+ * Tests that opening the elider button drop-down menu emits a render signal
+ * to indicate that the elided menu items were rendered.
+ */
+async function testBreadcrumbEliderButtonOpenEmitsRenderSignal(done) {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/C/D/E/F';
+
+  // Elider button drop-down menu should be in the 'closed' state.
+  const elider = getBreadCrumbEliderButton();
+  assertEquals('false', elider.getAttribute('aria-expanded'));
+
+  // Set the BreadCrumb signals callback.
+  const signal = [];
+  element.setSignalCallback((rendered) => {
+    assert(typeof rendered === 'string');
+    signal.push(rendered);
+  });
+
+  // Clicking the elider button should 'open' its drop-down menu.
+  assertFalse(elider.hasAttribute('hidden'));
+  elider.click();
+  assertEquals('true', elider.getAttribute('aria-expanded'));
+
+  // Signal the elided parts were rendered to the drop-down buttons.
+  assertEquals(signal[0], 'path-rendered');
+  assertEquals(1, signal.length);
+
+  done();
+}
+
+/**
+ * Tests that setting the path closes the the drop-down menu.
+ */
+function testBreadcrumbSetPathClosesEliderButtonDropDownMenu() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/C/D/E';
+
+  // Elider button drop-down menu should be in the 'closed' state.
+  const elider = getBreadCrumbEliderButton();
+  assertEquals('false', elider.getAttribute('aria-expanded'));
+
+  // Clicking the elider button should 'open' its drop-down menu.
+  assertFalse(elider.hasAttribute('hidden'));
+  elider.click();
+  assertEquals('true', elider.getAttribute('aria-expanded'));
+
+  // clang-format off
+  const opened = element.path +
+     ' 1: display:block id=first text=[A]' +
+     ' 2: display:flex elider[aria-expanded=true,aria-haspopup,aria-label]' +
+     ' dropdown-item: display:block text=[B]' +
+     ' dropdown-item: display:block text=[C]' +
+     ' 3: display:none id=second hidden' +
+     ' 4: display:block id=third text=[D]' +
+     ' 5: display:block id=fourth text=[E]';
+  // clang-format on
+
+  const first = element.parts.join('/');
+  assertEquals(opened, first + ' ' + getBreadCrumbButtonState());
+
+  // Changing the path should 'close' the drop-down menu.
+  element.path = 'F/G/H';
+  assertEquals('false', elider.getAttribute('aria-expanded'));
+
+  // clang-format off
+  const closed = element.path +
+    ' 1: display:block id=first text=[F]' +
+    ' 2: display:none elider[aria-expanded=false,aria-haspopup,aria-label,hidden]' +
+    ' 3: display:none id=second hidden' +
+    ' 4: display:block id=third text=[G]' +
+    ' 5: display:block id=fourth text=[H]';
+  // clang-format on
+
+  const second = element.parts.join('/');
+  assertEquals(closed, second + ' ' + getBreadCrumbButtonState());
+}
+
+/**
+ * Tests that setting the path updates the <bread-crumb path> attribute.
+ */
+function testBreadcrumbSetPathChangesElementPath() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/C/D/E/F';
+  assertEquals(element.path, element.getAttribute('path'));
+
+  // Change path.
+  element.path = 'G/H/I';
+  assertEquals(element.path, element.getAttribute('path'));
+}
+
+/**
+ * Tests that opening and closing the elider button drop-down menu adds and
+ * removes <bread-crumb checked> attribute.
+ */
+function testBreadcrumbEliderButtonOpenCloseChangesElementChecked() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/C/D/E/F';
+
+  // Elider button drop-down menu should be in the 'closed' state.
+  const elider = getBreadCrumbEliderButton();
+  assertEquals('false', elider.getAttribute('aria-expanded'));
+  assertFalse(element.hasAttribute('checked'));
+
+  // Clicking the elider button should 'open' its drop-down menu.
+  assertFalse(elider.hasAttribute('hidden'));
+  elider.click();
+  assertEquals('true', elider.getAttribute('aria-expanded'));
+  assert(element.hasAttribute('checked'));
+
+  // Change path.
+  element.path = 'G/H/I/J/K';
+
+  // Changing the path should 'close' the drop-down menu.
+  assertEquals('false', elider.getAttribute('aria-expanded'));
+  assertFalse(element.hasAttribute('checked'));
+}
+
+/**
+ * Tests that opening and closing the elider button drop-down menu adds and
+ * removes global <html> element state.
+ */
+function testBreadcrumbEliderButtonOpenCloseChangesGlobalState() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/C/D/E/F';
+
+  // Elider button drop-down menu should be in the 'closed' state.
+  const elider = getBreadCrumbEliderButton();
+  assertEquals('false', elider.getAttribute('aria-expanded'));
+
+  // Clicking the elider button should 'open' its drop-down menu.
+  assertFalse(elider.hasAttribute('hidden'));
+  elider.click();
+  assertEquals('true', elider.getAttribute('aria-expanded'));
+
+  // And also change the global element state.
+  const root = document.documentElement;
+  assertTrue(root.classList.contains('breadcrumb-elider-expanded'));
+
+  // Change path.
+  element.path = 'G/H/I/J/K';
+
+  // Changing the path should 'close' the drop-down menu.
+  assertEquals('false', elider.getAttribute('aria-expanded'));
+  assertFalse(element.hasAttribute('checked'));
+
+  // And clear the global element state.
+  assertFalse(root.classList.contains('breadcrumb-elider-expanded'));
+}
+
+/**
+ * Tests that wide text path components are rendered elided with ellipsis ...
+ * an opportunity for adding a tooltip.
+ */
+function testBreadcrumbPartPartsEllipsisElide() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/VERYVERYVERYVERYWIDEPATHPART';
+
+  // clang-format off
+  const expect = element.path +
+      ' 1: display:block id=first text=[A]' +
+      ' 2: display:none elider[aria-expanded=false,aria-haspopup,aria-label,hidden]' +
+      ' 3: display:none id=second hidden' +
+      ' 4: display:none id=third hidden' +
+      ' 5: display:block id=fourth text=[VERYVERYVERYVERYWIDEPATHPART]';
+  // clang-format on
+
+  const path = element.parts.join('/');
+  assertEquals(expect, path + ' ' + getBreadCrumbButtonState());
+
+  // The wide part should render its text with ellipsis.
+  let ellipsis = element.getEllipsisButtons();
+  assertEquals(1, ellipsis.length);
+  assertEquals(element.parts[1], ellipsis[0].textContent);
+
+  // Add has-tooltip attribute to this ellipsis button.
+  ellipsis[0].setAttribute('has-tooltip', '');
+  const tooltip = element.getToolTipButtons();
+  assertEquals(ellipsis[0], tooltip[0]);
+
+  // getEllipsisButtons() should ignore [has-tooltip] buttons.
+  ellipsis = element.getEllipsisButtons();
+  assertEquals(0, ellipsis.length);
+}
+
+/**
+ * Tests that wide text path components in the drop-down menu are rendered
+ * elided with ellipsis ... an opportunity for adding a tooltip.
+ */
+function testBreadcrumbDropDownMenuPathPartsEllipsisElide() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/VERYVERYVERYVERYWIDEPATHPART/C/D';
+
+  // clang-format off
+  const expect = element.path +
+      ' 1: display:block id=first text=[A]' +
+      ' 2: display:flex elider[aria-expanded=false,aria-haspopup,aria-label]' +
+      ' dropdown-item: display:block text=[B]' +
+      ' dropdown-item: display:block text=[VERYVERYVERYVERYWIDEPATHPART]' +
+      ' 3: display:none id=second hidden' +
+      ' 4: display:block id=third text=[C]' +
+      ' 5: display:block id=fourth text=[D]';
+  // clang-format on
+
+  const path = element.parts.join('/');
+  assertEquals(expect, path + ' ' + getBreadCrumbButtonState());
+
+  // The wide part button should render its text with ellipsis.
+  let ellipsis = element.getEllipsisButtons();
+  assertEquals(1, ellipsis.length);
+  assertEquals(element.parts[2], ellipsis[0].textContent);
+
+  // Add a has-tooltip attribute to the ellipsis button.
+  ellipsis[0].setAttribute('has-tooltip', '');
+  const tooltip = element.getToolTipButtons();
+  assertEquals(ellipsis[0], tooltip[0]);
+
+  // getEllipsisButtons() should ignore [has-tooltip] buttons.
+  ellipsis = element.getEllipsisButtons();
+  assertEquals(0, ellipsis.length);
+}
+
+/**
+ * Tests that breadcrumb getToolTipButtons() service returns all buttons that
+ * have a [has-tooltip] attribute.
+ */
+function testBreadcrumbButtonHasToolTipAttribute() {
+  const element = getBreadCrumb();
+
+  // Set path.
+  element.path = 'A/B/C/D/E';
+
+  // Add a tool tip to the visible main buttons.
+  getVisibleBreadCrumbMainButtons().forEach((button) => {
+    button.setAttribute('has-tooltip', '');
+  });
+
+  // getToolTipButtons() should return those main buttons.
+  let tooltips = element.getToolTipButtons();
+  assertEquals('A', tooltips[0].textContent);
+  assertEquals('D', tooltips[1].textContent);
+  assertEquals('E', tooltips[2].textContent);
+  assertEquals(3, tooltips.length);
+
+  // Changing the path should clear all tool tips.
+  element.path = 'G/H/I/J/K';
+  assertEquals(0, element.getToolTipButtons().length);
+
+  // Add tool tips to the drop-down menu buttons.
+  getBreadCrumbMenuButtons().forEach((button) => {
+    button.setAttribute('has-tooltip', '');
+  });
+
+  // getToolTipButtons() should return those menu buttons.
+  tooltips = element.getToolTipButtons();
+  assertEquals('H', tooltips[0].textContent);
+  assertEquals('I', tooltips[1].textContent);
+  assertEquals(2, tooltips.length);
+
+  // Note: tool tips can be enabled for the elider button.
+  const elider = getBreadCrumbEliderButton();
+  elider.setAttribute('has-tooltip', '');
+
+  // But getToolTipButtons() must exclude the elider (i18n).
+  tooltips = element.getToolTipButtons();
+  assertEquals('H', tooltips[0].textContent);
+  assertEquals('I', tooltips[1].textContent);
+  assertEquals(2, tooltips.length);
+
+  // And changing path should not clear its tool tip (i18n).
+  element.path = 'since/the/elider/has/an/i18n/tooltip/aria-label';
+  assertEquals(0, element.getToolTipButtons().length);
+  assertTrue(elider.hasAttribute('has-tooltip'));
+
+  // getEllipsisButtons() must exclude the elider button.
+  element.path = elider.getAttribute('aria-label');
+  const ellipsis = element.getEllipsisButtons();
+  assertEquals(getVisibleBreadCrumbMainButtons()[0], ellipsis[0]);
+  assertNotEqual(elider, ellipsis[0]);
+  assertEquals(1, ellipsis.length);
+}
diff --git a/ui/file_manager/file_manager/main.html b/ui/file_manager/file_manager/main.html
index 8ab3df9..2c4b50e 100644
--- a/ui/file_manager/file_manager/main.html
+++ b/ui/file_manager/file_manager/main.html
@@ -444,14 +444,14 @@
           <files-toggle-ripple></files-toggle-ripple>
           <div class="icon"></div>
         </button>
-        <cr-button id="selection-menu-button" class="icon-button menu-button" tabindex="0"
+        <button id="selection-menu-button" class="icon-button menu-button" tabindex="0"
                 menu="#file-context-menu"
                 aria-label="$i18n{SELECTION_MENU_BUTTON_TOOLTIP}"
                 aria-activedescendant="file-context-menu"
                 has-tooltip hidden>
           <files-toggle-ripple></files-toggle-ripple>
           <div class="icon"></div>
-        </cr-button>
+        </button>
       </div>
     </div>
 
diff --git a/ui/file_manager/integration_tests/file_manager/quick_view.js b/ui/file_manager/integration_tests/file_manager/quick_view.js
index 81c892c..ea196329 100644
--- a/ui/file_manager/integration_tests/file_manager/quick_view.js
+++ b/ui/file_manager/integration_tests/file_manager/quick_view.js
@@ -305,7 +305,12 @@
     // Open the file in Quick View.
     await openQuickView(appId, ENTRIES.hello.nameText);
 
-    // Check: the correct mimeType should be displayed.
+    // Check: the correct mimeType should be displayed (note: MIME type
+    // identification differs depending on the metadata provider for the
+    // underlying volume. Here, it is reported as text/plain for ENTRIES.hello,
+    // because the file is on Drive, later (see
+    // openQuickViewTextFileWithUnknownMimeType) it is not reported because the
+    // file is on the local filesystem).
     const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
     chrome.test.assertEq('text/plain', mimeType);
   };
@@ -523,8 +528,8 @@
   };
 
   /**
-   * Tests opening Quick View with a document identified as text from file
-   * sniffing because it has no filename extension.
+   * Tests opening Quick View with a local text document identified as text from
+   * file sniffing (the first word of the file is "From ", note trailing space).
    */
   testcase.openQuickViewSniffedText = async () => {
     const caller = getCaller();
@@ -564,6 +569,51 @@
   };
 
   /**
+   * Tests opening Quick View with a local text document whose MIME type cannot
+   * be identified by MIME type sniffing.
+   */
+  testcase.openQuickViewTextFileWithUnknownMimeType = async () => {
+    const caller = getCaller();
+
+    /**
+     * The text <webview> resides in the #quick-view shadow DOM, as a child of
+     * the #dialog element.
+     */
+    const webView = ['#quick-view', '#dialog[open] webview.text-content'];
+
+    // Open Files app on Downloads containing ENTRIES.hello.
+    const appId =
+        await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
+
+    // Open the file in Quick View.
+    await openQuickView(appId, ENTRIES.hello.nameText);
+
+    // Wait for the Quick View <webview> to load and display its content.
+    function checkWebViewTextLoaded(elements) {
+      let haveElements = Array.isArray(elements) && elements.length === 1;
+      if (haveElements) {
+        haveElements = elements[0].styles.display.includes('block');
+      }
+      if (!haveElements || !elements[0].attributes.src) {
+        return pending(caller, 'Waiting for <webview> to load.');
+      }
+      return;
+    }
+    await repeatUntil(async () => {
+      return checkWebViewTextLoaded(await remoteCall.callRemoteTestUtil(
+          'deepQueryAllElements', appId, [webView, ['display']]));
+    });
+
+    // Check: no mimeType information is displayed. Note that there are multiple
+    // levels of shadow DOM present in this query.
+    const mimeTypeQuery = [
+      '#quick-view', '#dialog[open] files-metadata-box[metadata~="mime"]',
+      'files-metadata-entry[key="Type"]', '#box[hidden]'
+    ];
+    await remoteCall.waitForElement(appId, mimeTypeQuery);
+  };
+
+  /**
    * Tests opening Quick View and scrolling its <webview> which contains a tall
    * text document.
    */
@@ -680,6 +730,10 @@
 
     // Check: the <webview> embed type should be PDF mime type.
     chrome.test.assertEq('application/pdf', type);
+
+    // Check: the correct mimeType should be displayed.
+    const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
+    chrome.test.assertEq('application/pdf', mimeType);
   };
 
   /**
@@ -759,6 +813,10 @@
       return checkWebViewTextLoaded(await remoteCall.callRemoteTestUtil(
           'deepQueryAllElements', appId, [webView, ['display']]));
     });
+
+    // Check: the correct mimeType should be displayed.
+    const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
+    chrome.test.assertEq('text/plain', mimeType);
   };
 
   /**
@@ -825,6 +883,14 @@
       return checkQuickViewHtmlScrollY(await remoteCall.callRemoteTestUtil(
           'deepExecuteScriptInWebView', appId, [webView, getScrollY]));
     });
+
+    // Check: no mimeType information is displayed. Note that there are multiple
+    // levels of shadow DOM present in this query.
+    const mimeTypeQuery = [
+      '#quick-view', '#dialog[open] files-metadata-box[metadata~="mime"]',
+      'files-metadata-entry[key="Type"]', '#box[hidden]'
+    ];
+    await remoteCall.waitForElement(appId, mimeTypeQuery);
   };
 
   /**
@@ -915,6 +981,10 @@
 
     // Check: the <webview> body backgroundColor should be transparent black.
     chrome.test.assertEq('rgba(0, 0, 0, 0)', backgroundColor[0]);
+
+    // Check: the correct mimeType should be displayed.
+    const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
+    chrome.test.assertEq('audio/ogg', mimeType);
   };
 
   /**
@@ -1013,6 +1083,10 @@
           'deepQueryAllElements', appId, [albumArtWebView, ['display']]));
     });
 
+    // Check: the correct mimeType should be displayed.
+    const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
+    chrome.test.assertEq('audio/mpeg', mimeType);
+
     // Check: the audio album metadata should also be displayed.
     const album = await getQuickViewMetadataBoxField(appId, 'Album');
     chrome.test.assertEq(album, 'OK Computer');
@@ -1062,6 +1136,10 @@
 
     // Check: the <webview> body backgroundColor should be transparent black.
     chrome.test.assertEq('rgba(0, 0, 0, 0)', backgroundColor[0]);
+
+    // Check: the correct mimeType should be displayed.
+    const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
+    chrome.test.assertEq('image/jpeg', mimeType);
   };
 
   /**
@@ -1208,6 +1286,10 @@
     // Check: the Dimensions shown in the metadata box are correct.
     const size = await getQuickViewMetadataBoxField(appId, 'Dimensions');
     chrome.test.assertEq('1324 x 4028', size);
+
+    // Check: the correct mimeType should be displayed.
+    const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
+    chrome.test.assertEq('image/tiff', mimeType);
   };
 
   /**
@@ -1361,6 +1443,10 @@
     // Check: the <webview> body backgroundColor should be transparent black.
     chrome.test.assertEq('rgba(0, 0, 0, 0)', backgroundColor[0]);
 
+    // Check: the correct mimeType should be displayed.
+    const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
+    chrome.test.assertEq('video/webm', mimeType);
+
     // Close Quick View.
     await closeQuickView(appId);
 
@@ -2725,17 +2811,19 @@
     const ctrlA = ['#file-list', 'a', true, false, false];
     await remoteCall.fakeKeyDown(appId, ...ctrlA);
 
+    const caller = getCaller();
+
     // Wait until the selection menu is visible.
-    function checkElementsDisplayFlex(elements) {
+    function checkElementsDisplayVisible(elements) {
       chrome.test.assertTrue(Array.isArray(elements));
-      if (elements.length == 0 || elements[0].styles.display !== 'flex') {
+      if (elements.length == 0 || elements[0].styles.display === 'none') {
         return pending(caller, 'Waiting for Selection Menu to be visible.');
       }
     }
 
     await repeatUntil(async () => {
       const elements = ['#selection-menu-button'];
-      return checkElementsDisplayFlex(await remoteCall.callRemoteTestUtil(
+      return checkElementsDisplayVisible(await remoteCall.callRemoteTestUtil(
           'deepQueryAllElements', appId, [elements, ['display']]));
     });
 
@@ -2764,7 +2852,6 @@
         '#file-context-menu:not([hidden]) [command="#get-info"]:not([hidden])');
 
     // Check: the Quick View dialog should be shown.
-    const caller = getCaller();
     await repeatUntil(async () => {
       const query = ['#quick-view', '#dialog[open]'];
       const elements = await remoteCall.callRemoteTestUtil(
@@ -2809,16 +2896,16 @@
     await remoteCall.fakeKeyDown(appId, ...ctrlA);
 
     // Wait until the selection menu is visible.
-    function checkElementsDisplayFlex(elements) {
+    function checkElementsDisplayVisible(elements) {
       chrome.test.assertTrue(Array.isArray(elements));
-      if (elements.length == 0 || elements[0].styles.display !== 'flex') {
+      if (elements.length == 0 || elements[0].styles.display === 'none') {
         return pending(caller, 'Waiting for Selection Menu to be visible.');
       }
     }
 
     await repeatUntil(async () => {
       const elements = ['#selection-menu-button'];
-      return checkElementsDisplayFlex(await remoteCall.callRemoteTestUtil(
+      return checkElementsDisplayVisible(await remoteCall.callRemoteTestUtil(
           'deepQueryAllElements', appId, [elements, ['display']]));
     });
 
diff --git a/ui/native_theme/BUILD.gn b/ui/native_theme/BUILD.gn
index 148189f..08bf355 100644
--- a/ui/native_theme/BUILD.gn
+++ b/ui/native_theme/BUILD.gn
@@ -128,7 +128,10 @@
 }
 
 test("native_theme_unittests") {
-  sources = [ "native_theme_unittest.cc" ]
+  sources = [
+    "native_theme_unittest.cc",
+    "themed_vector_icon_unittest.cc",
+  ]
 
   if (use_aura) {
     sources += [ "native_theme_aura_unittest.cc" ]
diff --git a/ui/native_theme/themed_vector_icon.cc b/ui/native_theme/themed_vector_icon.cc
index 0750002a..aa4993a5 100644
--- a/ui/native_theme/themed_vector_icon.cc
+++ b/ui/native_theme/themed_vector_icon.cc
@@ -12,17 +12,42 @@
 ThemedVectorIcon::ThemedVectorIcon() = default;
 
 ThemedVectorIcon::ThemedVectorIcon(const gfx::VectorIcon* icon,
-                                   NativeTheme::ColorId color_id)
-    : icon_(icon), color_id_(color_id) {}
+                                   NativeTheme::ColorId color_id,
+                                   int icon_size)
+    : icon_(icon), icon_size_(icon_size), color_id_(color_id) {}
 
-ThemedVectorIcon::ThemedVectorIcon(const gfx::VectorIcon* icon, SkColor color)
-    : icon_(icon), color_(color) {}
+ThemedVectorIcon::ThemedVectorIcon(const VectorIconModel& vector_icon_model)
+    : icon_size_(vector_icon_model.icon_size()) {
+  if (vector_icon_model.has_color()) {
+    ThemedVectorIcon(vector_icon_model.vector_icon(),
+                     vector_icon_model.color());
+  } else if (vector_icon_model.color_id() >= 0) {
+    ThemedVectorIcon(
+        vector_icon_model.vector_icon(),
+        static_cast<ui::NativeTheme::ColorId>(vector_icon_model.color_id()));
+  } else {
+    ThemedVectorIcon(vector_icon_model.vector_icon());
+  }
+}
 
-ThemedVectorIcon::ThemedVectorIcon(const ThemedVectorIcon& other) = default;
+ThemedVectorIcon::ThemedVectorIcon(const gfx::VectorIcon* icon,
+                                   SkColor color,
+                                   int icon_size)
+    : icon_(icon), icon_size_(icon_size), color_(color) {}
+
+ThemedVectorIcon::ThemedVectorIcon(const ThemedVectorIcon&) = default;
+
+ThemedVectorIcon& ThemedVectorIcon::operator=(const ThemedVectorIcon&) =
+    default;
+
+ThemedVectorIcon::ThemedVectorIcon(ThemedVectorIcon&&) = default;
+
+ThemedVectorIcon& ThemedVectorIcon::operator=(ThemedVectorIcon&&) = default;
 
 const gfx::ImageSkia ThemedVectorIcon::GetImageSkia(NativeTheme* theme) const {
   DCHECK(!empty());
-  return CreateVectorIcon(*icon_, GetColor(theme));
+  return icon_size_ > 0 ? CreateVectorIcon(*icon_, icon_size_, GetColor(theme))
+                        : CreateVectorIcon(*icon_, GetColor(theme));
 }
 
 const gfx::ImageSkia ThemedVectorIcon::GetImageSkia(NativeTheme* theme,
@@ -33,7 +58,8 @@
 
 const gfx::ImageSkia ThemedVectorIcon::GetImageSkia(SkColor color) const {
   DCHECK(!empty());
-  return CreateVectorIcon(*icon_, color);
+  return icon_size_ > 0 ? CreateVectorIcon(*icon_, icon_size_, color)
+                        : CreateVectorIcon(*icon_, color);
 }
 
 SkColor ThemedVectorIcon::GetColor(NativeTheme* theme) const {
diff --git a/ui/native_theme/themed_vector_icon.h b/ui/native_theme/themed_vector_icon.h
index 31779e72..fc056d54 100644
--- a/ui/native_theme/themed_vector_icon.h
+++ b/ui/native_theme/themed_vector_icon.h
@@ -7,6 +7,7 @@
 
 #include "base/optional.h"
 #include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/models/image_model.h"
 #include "ui/native_theme/native_theme.h"
 #include "ui/native_theme/native_theme_export.h"
 
@@ -22,11 +23,20 @@
   ThemedVectorIcon();
   explicit ThemedVectorIcon(
       const gfx::VectorIcon* icon,
-      NativeTheme::ColorId color_id = NativeTheme::kColorId_DefaultIconColor);
+      NativeTheme::ColorId color_id = NativeTheme::kColorId_DefaultIconColor,
+      int icon_size = 0);
+  explicit ThemedVectorIcon(const VectorIconModel& vector_icon_model);
   // TODO (kylixrd): Remove this once all the hard-coded uses of color are
   // removed.
-  ThemedVectorIcon(const gfx::VectorIcon* icon, SkColor color);
+  ThemedVectorIcon(const gfx::VectorIcon* icon,
+                   SkColor color,
+                   int icon_size = 0);
+
+  // Copyable and moveable
   ThemedVectorIcon(const ThemedVectorIcon& other);
+  ThemedVectorIcon& operator=(const ThemedVectorIcon&);
+  ThemedVectorIcon(ThemedVectorIcon&&);
+  ThemedVectorIcon& operator=(ThemedVectorIcon&&);
 
   void clear() { icon_ = nullptr; }
   bool empty() const { return !icon_; }
@@ -38,6 +48,7 @@
   SkColor GetColor(NativeTheme* theme) const;
 
   const gfx::VectorIcon* icon_ = nullptr;
+  int icon_size_ = 0;
   base::Optional<NativeTheme::ColorId> color_id_;
   base::Optional<SkColor> color_;
 };
diff --git a/ui/native_theme/themed_vector_icon_unittest.cc b/ui/native_theme/themed_vector_icon_unittest.cc
new file mode 100644
index 0000000..22e9814
--- /dev/null
+++ b/ui/native_theme/themed_vector_icon_unittest.cc
@@ -0,0 +1,50 @@
+// 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 "ui/native_theme/themed_vector_icon.h"
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/gfx/vector_icon_types.h"
+
+namespace ui {
+
+namespace {
+
+const gfx::VectorIcon* GetVectorIcon() {
+  static constexpr gfx::PathElement path[] = {gfx::CommandType::CIRCLE, 24, 18,
+                                              5};
+  static const gfx::VectorIconRep rep[] = {{path, 4}};
+  static constexpr gfx::VectorIcon circle_icon = {rep, 1, "circle"};
+
+  return &circle_icon;
+}
+
+}  // namespace
+
+TEST(ThemedVectorIconTest, DefaultEmpty) {
+  ThemedVectorIcon vector_icon;
+
+  EXPECT_TRUE(vector_icon.empty());
+}
+
+TEST(ThemedVectorIconTest, CheckForVectorIcon) {
+  ThemedVectorIcon vector_icon = ThemedVectorIcon(GetVectorIcon());
+
+  EXPECT_FALSE(vector_icon.empty());
+}
+
+TEST(ImageModelTest, CheckAssign) {
+  ThemedVectorIcon vector_icon_dest;
+  ThemedVectorIcon vector_icon_src(GetVectorIcon());
+
+  EXPECT_TRUE(vector_icon_dest.empty());
+  EXPECT_FALSE(vector_icon_src.empty());
+
+  vector_icon_dest = vector_icon_src;
+  EXPECT_FALSE(vector_icon_dest.empty());
+}
+
+}  // namespace ui
diff --git a/ui/views/accessibility/ax_virtual_view.cc b/ui/views/accessibility/ax_virtual_view.cc
index ea292416..a222443 100644
--- a/ui/views/accessibility/ax_virtual_view.cc
+++ b/ui/views/accessibility/ax_virtual_view.cc
@@ -17,6 +17,7 @@
 #include "ui/accessibility/ax_action_data.h"
 #include "ui/accessibility/ax_tree_data.h"
 #include "ui/accessibility/platform/ax_platform_node.h"
+#include "ui/base/layout.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/views/accessibility/view_accessibility.h"
diff --git a/ui/views/controls/combobox/combobox.cc b/ui/views/controls/combobox/combobox.cc
index 1137efc4..81415ef 100644
--- a/ui/views/controls/combobox/combobox.cc
+++ b/ui/views/controls/combobox/combobox.cc
@@ -15,6 +15,7 @@
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/ime/input_method.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/models/menu_model.h"
 #include "ui/events/event.h"
 #include "ui/gfx/canvas.h"
@@ -176,7 +177,9 @@
 
   int GetGroupIdAt(int index) const override { return -1; }
 
-  bool GetIconAt(int index, gfx::Image* icon) const override { return false; }
+  ui::ImageModel GetIconAt(int index) const override {
+    return ui::ImageModel();
+  }
 
   ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override {
     return nullptr;
diff --git a/ui/views/controls/editable_combobox/editable_combobox.cc b/ui/views/controls/editable_combobox/editable_combobox.cc
index 8a0fef0..5905c03 100644
--- a/ui/views/controls/editable_combobox/editable_combobox.cc
+++ b/ui/views/controls/editable_combobox/editable_combobox.cc
@@ -226,7 +226,9 @@
 
   int GetGroupIdAt(int index) const override { return -1; }
 
-  bool GetIconAt(int index, gfx::Image* icon) const override { return false; }
+  ui::ImageModel GetIconAt(int index) const override {
+    return ui::ImageModel();
+  }
 
   ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override {
     return nullptr;
diff --git a/ui/views/controls/menu/menu_model_adapter.cc b/ui/views/controls/menu/menu_model_adapter.cc
index 88a868c..cbf92887 100644
--- a/ui/views/controls/menu/menu_model_adapter.cc
+++ b/ui/views/controls/menu/menu_model_adapter.cc
@@ -104,15 +104,17 @@
                                model->GetSeparatorTypeAt(model_index));
   }
 
-  gfx::Image icon;
-  model->GetIconAt(model_index, &icon);
+  ui::ImageModel icon = model->GetIconAt(model_index);
+  ui::ImageModel minor_icon = model->GetMinorIconAt(model_index);
   return menu->AddMenuItemAt(
       menu_index, item_id, model->GetLabelAt(model_index),
       model->GetMinorTextAt(model_index),
-      ui::ThemedVectorIcon(model->GetMinorIconAt(model_index)),
-      icon.IsEmpty() ? gfx::ImageSkia() : *icon.ToImageSkia(),
-      icon.IsEmpty() ? ui::ThemedVectorIcon(model->GetVectorIconAt(model_index))
-                     : ui::ThemedVectorIcon(),
+      minor_icon.IsVectorIcon()
+          ? ui::ThemedVectorIcon(minor_icon.GetVectorIcon())
+          : ui::ThemedVectorIcon(),
+      icon.IsImage() ? *icon.GetImage().ToImageSkia() : gfx::ImageSkia(),
+      icon.IsVectorIcon() ? ui::ThemedVectorIcon(icon.GetVectorIcon())
+                          : ui::ThemedVectorIcon(),
       *type, ui::NORMAL_SEPARATOR);
 }
 
diff --git a/ui/views/controls/menu/menu_model_adapter_unittest.cc b/ui/views/controls/menu/menu_model_adapter_unittest.cc
index 7f9bb3bd..6241a9d 100644
--- a/ui/views/controls/menu/menu_model_adapter_unittest.cc
+++ b/ui/views/controls/menu/menu_model_adapter_unittest.cc
@@ -66,7 +66,9 @@
 
   int GetGroupIdAt(int index) const override { return 0; }
 
-  bool GetIconAt(int index, gfx::Image* icon) const override { return false; }
+  ui::ImageModel GetIconAt(int index) const override {
+    return ui::ImageModel();
+  }
 
   ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override {
     return nullptr;
diff --git a/ui/views/test/views_test_base.cc b/ui/views/test/views_test_base.cc
index 016f196..762e6c94 100644
--- a/ui/views/test/views_test_base.cc
+++ b/ui/views/test/views_test_base.cc
@@ -131,9 +131,8 @@
 
 std::unique_ptr<Widget> ViewsTestBase::CreateTestWidget(
     Widget::InitParams::Type type) {
-  Widget::InitParams params = CreateParamsForTestWidget(type);
-  auto widget = std::make_unique<Widget>();
-  widget->Init(std::move(params));
+  std::unique_ptr<Widget> widget = AllocateTestWidget();
+  widget->Init(CreateParamsForTestWidget(type));
   return widget;
 }
 
@@ -183,6 +182,10 @@
 #endif
 }
 
+std::unique_ptr<Widget> ViewsTestBase::AllocateTestWidget() {
+  return std::make_unique<Widget>();
+}
+
 Widget::InitParams ViewsTestBase::CreateParamsForTestWidget(
     Widget::InitParams::Type type) {
   Widget::InitParams params = CreateParams(type);
diff --git a/ui/views/test/views_test_base.h b/ui/views/test/views_test_base.h
index e2acd16..68bfb94 100644
--- a/ui/views/test/views_test_base.h
+++ b/ui/views/test/views_test_base.h
@@ -68,8 +68,10 @@
 
   void RunPendingMessages();
 
-  // Creates a widget of |type| with any platform specific data for use in
-  // cross-platform tests.
+  // Returns CreateParams for a widget of type |type|.  This is used by
+  // CreateParamsForTestWidget() and thus by CreateTestWidget(), and may also be
+  // used directly.  The default implementation sets the context to
+  // GetContext().
   virtual Widget::InitParams CreateParams(Widget::InitParams::Type type);
 
   virtual std::unique_ptr<Widget> CreateTestWidget(
@@ -111,8 +113,7 @@
   }
 #endif
 
-  // Returns a context view. In aura builds, this will be the
-  // RootWindow. Everywhere else, NULL.
+  // Returns a context view. In aura builds, this will be the RootWindow.
   gfx::NativeWindow GetContext();
 
   // Factory for creating the native widget when |native_widget_type_| is set to
@@ -121,6 +122,12 @@
       const Widget::InitParams& init_params,
       internal::NativeWidgetDelegate* delegate);
 
+  // Instantiates a Widget for CreateTestWidget(), but does no other
+  // initialization.  Overriding this allows subclasses to customize the Widget
+  // subclass returned from CreateTestWidget().
+  virtual std::unique_ptr<Widget> AllocateTestWidget();
+
+  // Constructs the params for CreateTestWidget().
   Widget::InitParams CreateParamsForTestWidget(
       Widget::InitParams::Type type =
           Widget::InitParams::TYPE_WINDOW_FRAMELESS);
diff --git a/ui/views/widget/widget_interactive_uitest.cc b/ui/views/widget/widget_interactive_uitest.cc
index 0f50819e3..1eaf7ae 100644
--- a/ui/views/widget/widget_interactive_uitest.cc
+++ b/ui/views/widget/widget_interactive_uitest.cc
@@ -29,6 +29,7 @@
 #include "ui/events/event_utils.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/gfx/native_widget_types.h"
+#include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/controls/textfield/textfield.h"
 #include "ui/views/controls/textfield/textfield_test_api.h"
 #include "ui/views/focus/focus_manager.h"
@@ -380,6 +381,39 @@
   widget1->CloseNow();
 }
 
+// Verifies bubbles result in a focus lost when shown.
+TEST_F(DesktopWidgetTestInteractive, FocusChangesOnBubble) {
+  Widget* widget = CreateWidget();
+  View* focusable_view =
+      widget->GetContentsView()->AddChildView(std::make_unique<View>());
+  focusable_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
+  widget->Show();
+  focusable_view->RequestFocus();
+  EXPECT_TRUE(focusable_view->HasFocus());
+
+  // Show a bubble.
+  auto owned_bubble_delegate_view =
+      std::make_unique<views::BubbleDialogDelegateView>(focusable_view,
+                                                        BubbleBorder::NONE);
+  owned_bubble_delegate_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
+  BubbleDialogDelegateView* bubble_delegate_view =
+      owned_bubble_delegate_view.get();
+  BubbleDialogDelegateView::CreateBubble(owned_bubble_delegate_view.release())
+      ->Show();
+  bubble_delegate_view->RequestFocus();
+
+  // |focusable_view| should no longer have focus.
+  EXPECT_FALSE(focusable_view->HasFocus());
+  EXPECT_TRUE(bubble_delegate_view->HasFocus());
+
+  bubble_delegate_view->GetWidget()->CloseNow();
+
+  // Closing the bubble should result in focus going back to the contents view.
+  EXPECT_TRUE(focusable_view->HasFocus());
+
+  widget->CloseNow();
+}
+
 class TouchEventHandler : public ui::EventHandler {
  public:
   explicit TouchEventHandler(Widget* widget) : widget_(widget) {
diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc
index e932006..fbfa30c 100644
--- a/ui/views/widget/widget_unittest.cc
+++ b/ui/views/widget/widget_unittest.cc
@@ -343,79 +343,6 @@
 
 // TODO(sky): add coverage of ownership for the desktop variants.
 
-// Widget owns its NativeWidget, part 1: NativeWidget is a platform-native
-// widget.
-TEST_F(WidgetOwnershipTest, Ownership_WidgetOwnsPlatformNativeWidget) {
-  OwnershipTestState state;
-
-  auto widget = std::make_unique<OwnershipTestWidget>(&state);
-  Widget::InitParams params = CreateParamsForTestWidget();
-  params.native_widget = CreatePlatformNativeWidgetImpl(
-      params, widget.get(), kStubCapture, &state.native_widget_deleted);
-  widget->Init(std::move(params));
-
-  // Now delete the Widget, which should delete the NativeWidget.
-  widget.reset();
-
-  EXPECT_TRUE(state.widget_deleted);
-  EXPECT_TRUE(state.native_widget_deleted);
-
-  // TODO(beng): write test for this ownership scenario and the NativeWidget
-  //             being deleted out from under the Widget.
-}
-
-// Widget owns its NativeWidget, part 2: NativeWidget is a NativeWidget.
-TEST_F(WidgetOwnershipTest, Ownership_WidgetOwnsViewsNativeWidget) {
-  OwnershipTestState state;
-
-  auto widget = std::make_unique<OwnershipTestWidget>(&state);
-  Widget::InitParams params = CreateParamsForTestWidget();
-  params.native_widget = CreatePlatformNativeWidgetImpl(
-      params, widget.get(), kStubCapture, &state.native_widget_deleted);
-  widget->Init(std::move(params));
-
-  // Now delete the Widget, which should delete the NativeWidget.
-  widget.reset();
-
-  EXPECT_TRUE(state.widget_deleted);
-  EXPECT_TRUE(state.native_widget_deleted);
-
-  // TODO(beng): write test for this ownership scenario and the NativeWidget
-  //             being deleted out from under the Widget.
-}
-
-// Widget owns its NativeWidget, part 3: NativeWidget is a NativeWidget,
-// destroy the parent view.
-TEST_F(WidgetOwnershipTest,
-       Ownership_WidgetOwnsViewsNativeWidget_DestroyParentView) {
-  OwnershipTestState state;
-
-  Widget* toplevel = CreateTopLevelPlatformWidget();
-
-  auto widget = std::make_unique<OwnershipTestWidget>(&state);
-  Widget::InitParams params = CreateParamsForTestWidget();
-  params.parent = toplevel->GetNativeView();
-  params.native_widget = CreatePlatformNativeWidgetImpl(
-      params, widget.get(), kStubCapture, &state.native_widget_deleted);
-  widget->Init(std::move(params));
-
-  // Now close the toplevel, which deletes the view hierarchy.
-  toplevel->CloseNow();
-
-  RunPendingMessages();
-
-  // This shouldn't delete the widget because it shouldn't be deleted
-  // from the native side.
-  EXPECT_FALSE(state.widget_deleted);
-  EXPECT_FALSE(state.native_widget_deleted);
-
-  // Now delete it explicitly.
-  widget.reset();
-
-  EXPECT_TRUE(state.widget_deleted);
-  EXPECT_TRUE(state.native_widget_deleted);
-}
-
 // NativeWidget owns its Widget, part 1: NativeWidget is a platform-native
 // widget.
 TEST_F(WidgetOwnershipTest, Ownership_PlatformNativeWidgetOwnsWidget) {
@@ -529,26 +456,72 @@
   EXPECT_TRUE(state.native_widget_deleted);
 }
 
-// Widget owns its NativeWidget and has a WidgetDelegateView as its contents.
-TEST_F(WidgetOwnershipTest,
-       Ownership_WidgetOwnsNativeWidgetWithWithWidgetDelegateView) {
-  OwnershipTestState state;
+class WidgetOwnsNativeWidgetTest : public WidgetOwnershipTest {
+ public:
+  WidgetOwnsNativeWidgetTest() = default;
+  ~WidgetOwnsNativeWidgetTest() override = default;
 
-  WidgetDelegateView* delegate_view = new WidgetDelegateView;
+  void TearDown() override {
+    EXPECT_TRUE(state_.widget_deleted);
+    EXPECT_TRUE(state_.native_widget_deleted);
 
-  auto widget = std::make_unique<OwnershipTestWidget>(&state);
+    WidgetOwnershipTest::TearDown();
+  }
+
+  OwnershipTestState* state() { return &state_; }
+
+ private:
+  OwnershipTestState state_;
+};
+
+// Widget owns its NativeWidget, part 1.
+TEST_F(WidgetOwnsNativeWidgetTest, Ownership) {
+  auto widget = std::make_unique<OwnershipTestWidget>(state());
   Widget::InitParams params = CreateParamsForTestWidget();
   params.native_widget = CreatePlatformNativeWidgetImpl(
-      params, widget.get(), kStubCapture, &state.native_widget_deleted);
-  params.delegate = delegate_view;
+      params, widget.get(), kStubCapture, &state()->native_widget_deleted);
   widget->Init(std::move(params));
-  widget->SetContentsView(delegate_view);
 
-  // Now delete the Widget. There should be no crash or use-after-free.
+  // Now delete the Widget, which should delete the NativeWidget.
   widget.reset();
 
-  EXPECT_TRUE(state.widget_deleted);
-  EXPECT_TRUE(state.native_widget_deleted);
+  // TODO(beng): write test for this ownership scenario and the NativeWidget
+  //             being deleted out from under the Widget.
+}
+
+// Widget owns its NativeWidget, part 2: destroy the parent view.
+TEST_F(WidgetOwnsNativeWidgetTest, DestroyParentView) {
+  Widget* toplevel = CreateTopLevelPlatformWidget();
+
+  auto widget = std::make_unique<OwnershipTestWidget>(state());
+  Widget::InitParams params = CreateParamsForTestWidget();
+  params.parent = toplevel->GetNativeView();
+  params.native_widget = CreatePlatformNativeWidgetImpl(
+      params, widget.get(), kStubCapture, &state()->native_widget_deleted);
+  widget->Init(std::move(params));
+
+  // Now close the toplevel, which deletes the view hierarchy.
+  toplevel->CloseNow();
+
+  RunPendingMessages();
+
+  // This shouldn't delete the widget because it shouldn't be deleted
+  // from the native side.
+  EXPECT_FALSE(state()->widget_deleted);
+  EXPECT_FALSE(state()->native_widget_deleted);
+}
+
+// Widget owns its NativeWidget, part 3: has a WidgetDelegateView as contents.
+TEST_F(WidgetOwnsNativeWidgetTest, WidgetDelegateView) {
+  auto widget = std::make_unique<OwnershipTestWidget>(state());
+  Widget::InitParams params = CreateParamsForTestWidget();
+  params.native_widget = CreatePlatformNativeWidgetImpl(
+      params, widget.get(), kStubCapture, &state()->native_widget_deleted);
+  params.delegate = new WidgetDelegateView();
+  widget->Init(std::move(params));
+
+  // Allow the Widget to go out of scope. There should be no crash or
+  // use-after-free.
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1259,37 +1232,6 @@
   EXPECT_FALSE(backspace_r.handled());
 }
 
-// Verifies bubbles result in a focus lost when shown.
-// TODO(msw): this tests relies on focus, it needs to be in
-// interactive_ui_tests.
-TEST_F(DesktopWidgetTest, DISABLED_FocusChangesOnBubble) {
-  // Create a widget, show and activate it and focus the contents view.
-  View* contents_view = new View;
-  contents_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
-  std::unique_ptr<Widget> widget = CreateTestWidget();
-  widget->SetContentsView(contents_view);
-  widget->Show();
-  widget->Activate();
-  contents_view->RequestFocus();
-  EXPECT_TRUE(contents_view->HasFocus());
-
-  // Show a bubble.
-  BubbleDialogDelegateView* bubble_delegate_view =
-      new TestBubbleDialogDelegateView(contents_view);
-  bubble_delegate_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
-  BubbleDialogDelegateView::CreateBubble(bubble_delegate_view)->Show();
-  bubble_delegate_view->RequestFocus();
-
-  // |contents_view_| should no longer have focus.
-  EXPECT_FALSE(contents_view->HasFocus());
-  EXPECT_TRUE(bubble_delegate_view->HasFocus());
-
-  bubble_delegate_view->GetWidget()->CloseNow();
-
-  // Closing the bubble should result in focus going back to the contents view.
-  EXPECT_TRUE(contents_view->HasFocus());
-}
-
 TEST_F(WidgetTest, BubbleControlsResetOnInit) {
   WidgetAutoclosePtr anchor(CreateTopLevelPlatformWidget());
   anchor->Show();
@@ -1332,8 +1274,10 @@
 // paints are expected.
 class DesktopAuraTestValidPaintWidget : public Widget, public WidgetObserver {
  public:
-  DesktopAuraTestValidPaintWidget() { observer_.Add(this); }
-
+  explicit DesktopAuraTestValidPaintWidget(Widget::InitParams init_params)
+      : Widget(std::move(init_params)) {
+    observer_.Add(this);
+  }
   ~DesktopAuraTestValidPaintWidget() override = default;
 
   bool ReadReceivedPaintAndReset() {
@@ -1385,12 +1329,11 @@
   std::unique_ptr<views::Widget> CreateTestWidget(
       views::Widget::InitParams::Type type =
           views::Widget::InitParams::TYPE_WINDOW_FRAMELESS) override {
-    Widget::InitParams params = CreateParamsForTestWidget(type);
-    auto widget = std::make_unique<DesktopAuraTestValidPaintWidget>();
+    auto widget = std::make_unique<DesktopAuraTestValidPaintWidget>(
+        CreateParamsForTestWidget(type));
     paint_widget_ = widget.get();
-    widget->Init(std::move(params));
 
-    View* contents_view = new View;
+    View* contents_view = new View();
     contents_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
     widget->SetContentsView(contents_view);
 
@@ -3874,48 +3817,31 @@
 // remaining top-level windows should be re-enabled.
 TEST_F(DesktopWidgetTest, WindowModalOwnerDestroyedEnabledTest) {
   // top_level_widget owns owner_dialog_widget which owns owned_dialog_widget.
-
-  // Create the top level widget.
   std::unique_ptr<Widget> top_level_widget = CreateTestWidget();
   top_level_widget->Show();
 
   // Create the owner modal dialog.
-  // owner_dialog_delegate instance will be destroyed when the dialog
-  // is destroyed.
-  ModalDialogDelegate* owner_dialog_delegate =
-      new ModalDialogDelegate(ui::MODAL_TYPE_WINDOW);
-
-  Widget owner_dialog_widget;
-  Widget::InitParams init_params =
-      CreateParamsForTestWidget(Widget::InitParams::TYPE_WINDOW);
-  init_params.delegate = owner_dialog_delegate;
-  init_params.parent = top_level_widget->GetNativeView();
-  init_params.native_widget =
-      new test::TestPlatformNativeWidget<DesktopNativeWidgetAura>(
-          &owner_dialog_widget, false, nullptr);
-  owner_dialog_widget.Init(std::move(init_params));
-
+  const auto create_params = [this](Widget* widget, gfx::NativeView parent) {
+    Widget::InitParams init_params =
+        CreateParamsForTestWidget(Widget::InitParams::TYPE_WINDOW);
+    init_params.delegate = new ModalDialogDelegate(ui::MODAL_TYPE_WINDOW);
+    init_params.parent = parent;
+    init_params.native_widget =
+        new test::TestPlatformNativeWidget<DesktopNativeWidgetAura>(
+            widget, false, nullptr);
+    return init_params;
+  };
+  Widget owner_dialog_widget(
+      create_params(&owner_dialog_widget, top_level_widget->GetNativeView()));
+  owner_dialog_widget.Show();
   HWND owner_hwnd = HWNDForWidget(&owner_dialog_widget);
 
-  owner_dialog_widget.Show();
-
   // Create the owned modal dialog.
-  // As above, the owned_dialog_instance instance will be destroyed
-  // when the dialog is destroyed.
-  ModalDialogDelegate* owned_dialog_delegate =
-      new ModalDialogDelegate(ui::MODAL_TYPE_WINDOW);
-
-  Widget owned_dialog_widget;
-  init_params.delegate = owned_dialog_delegate;
-  init_params.parent = owner_dialog_widget.GetNativeView();
-  init_params.native_widget =
-      new test::TestPlatformNativeWidget<DesktopNativeWidgetAura>(
-          &owned_dialog_widget, false, nullptr);
-  owned_dialog_widget.Init(std::move(init_params));
-
+  Widget owned_dialog_widget(
+      create_params(&owned_dialog_widget, owner_dialog_widget.GetNativeView()));
+  owned_dialog_widget.Show();
   HWND owned_hwnd = HWNDForWidget(&owned_dialog_widget);
 
-  owned_dialog_widget.Show();
   RunPendingMessages();
 
   HWND top_hwnd = HWNDForWidget(top_level_widget.get());
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn
index d94df2c..01084ff 100644
--- a/weblayer/BUILD.gn
+++ b/weblayer/BUILD.gn
@@ -150,6 +150,8 @@
     "browser/persistence/browser_persister.h",
     "browser/persistence/minimal_browser_persister.cc",
     "browser/persistence/minimal_browser_persister.h",
+    "browser/profile_disk_operations.cc",
+    "browser/profile_disk_operations.h",
     "browser/profile_impl.cc",
     "browser/profile_impl.h",
     "browser/ssl_error_controller_client.cc",
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
index b02a4e7..abe2388 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
@@ -333,20 +333,14 @@
     }
 
     /**
-     * Tests that a navigation that redirects to an external intent that can't be handled but has a
-     * fallback URL that launches an intent that *can* be handled results in the launching of the
-     * second intent.
      * |url| is a URL that redirects to an unhandleable intent but has a fallback URL that redirects
      * to a handleable intent.
-     * Tests that a navigation to |url| launches the handleable intent.
-     * TODO(crbug.com/1031465): Disallow such fallback intent launches by sharing Chrome's
-     * RedirectHandler impl, at which point this should fail and be updated to verify that the
-     * intent is blocked.
+     * Tests that a navigation to |url| blocks the handleable intent by policy on chained redirects.
      */
     @Test
     @SmallTest
     public void
-    testNonHandledExternalIntentWithFallbackUrlThatLaunchesIntentAfterRedirectLaunchesFallbackIntent()
+    testNonHandledExternalIntentWithFallbackUrlThatLaunchesIntentAfterRedirectBlocksFallbackIntent()
             throws Throwable {
         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
         IntentInterceptor intentInterceptor = new IntentInterceptor();
@@ -360,43 +354,43 @@
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> { tab.getNavigationController().navigate(Uri.parse(url)); });
 
-        intentInterceptor.waitForIntent();
+        NavigationWaiter waiter = new NavigationWaiter(
+                INTENT_TO_CHROME_URL, tab, /*expectFailure=*/true, /*waitForPaint=*/false);
+        waiter.waitForNavigation();
 
-        // The current URL should not have changed, and the intent should have been launched.
+        Assert.assertNull(intentInterceptor.mLastIntent);
+
+        // The current URL should not have changed.
         Assert.assertEquals(ABOUT_BLANK_URL, mActivityTestRule.getCurrentDisplayUrl());
-        Intent intent = intentInterceptor.mLastIntent;
-        Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
     }
 
     /**
      * Tests that going to a page that loads an intent that can be handled in onload() results in
-     * the external intent being launched.
-     * TODO(crbug.com/1031465): Disallow such intent launches by sharing Chrome's RedirectHandler
-     * impl, at which point this should fail and be updated to verify that the intent is blocked.
+     * the external intent being blocked by policy on intents without user gestures loading in the
+     * midst of a user-typed navigation.
      */
     @Test
     @SmallTest
-    public void testExternalIntentLaunchedViaOnLoad() throws Throwable {
+    public void testExternalIntentViaOnLoadBlocked() throws Throwable {
         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
         IntentInterceptor intentInterceptor = new IntentInterceptor();
         activity.setIntentInterceptor(intentInterceptor);
 
         String url = mActivityTestRule.getTestDataURL(PAGE_THAT_INTENTS_TO_CHROME_ON_LOAD_FILE);
 
-        mActivityTestRule.navigateAndWait(url);
+        Tab tab = mActivityTestRule.getActivity().getTab();
 
-        intentInterceptor.waitForIntent();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { tab.getNavigationController().navigate(Uri.parse(url)); });
 
-        // The current URL should not have changed, and the intent should have been launched.
+        NavigationWaiter waiter = new NavigationWaiter(
+                INTENT_TO_CHROME_URL, tab, /*expectFailure=*/true, /*waitForPaint=*/false);
+        waiter.waitForNavigation();
+
+        Assert.assertNull(intentInterceptor.mLastIntent);
+
+        // The current URL should not have changed.
         Assert.assertEquals(url, mActivityTestRule.getCurrentDisplayUrl());
-        Intent intent = intentInterceptor.mLastIntent;
-        Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
     }
 
     /**
diff --git a/weblayer/browser/content_browser_client_impl.cc b/weblayer/browser/content_browser_client_impl.cc
index d5dcec3..886ea50a 100644
--- a/weblayer/browser/content_browser_client_impl.cc
+++ b/weblayer/browser/content_browser_client_impl.cc
@@ -478,12 +478,14 @@
 #endif  // defined(OS_ANDROID)
 }
 
-void ContentBrowserClientImpl::ExposeInterfacesToMediaService(
-    service_manager::BinderRegistry* registry,
-    content::RenderFrameHost* render_frame_host) {
+void ContentBrowserClientImpl::BindMediaServiceReceiver(
+    content::RenderFrameHost* render_frame_host,
+    mojo::GenericPendingReceiver receiver) {
 #if defined(OS_ANDROID)
-  registry->AddInterface(
-      base::BindRepeating(&CreateMediaDrmStorage, render_frame_host));
+  if (auto r = receiver.As<media::mojom::MediaDrmStorage>()) {
+    CreateMediaDrmStorage(render_frame_host, std::move(r));
+    return;
+  }
 #endif
 }
 
diff --git a/weblayer/browser/content_browser_client_impl.h b/weblayer/browser/content_browser_client_impl.h
index 7f2a709..dea0603 100644
--- a/weblayer/browser/content_browser_client_impl.h
+++ b/weblayer/browser/content_browser_client_impl.h
@@ -80,9 +80,8 @@
       service_manager::BinderRegistry* registry,
       blink::AssociatedInterfaceRegistry* associated_registry,
       content::RenderProcessHost* render_process_host) override;
-  void ExposeInterfacesToMediaService(
-      service_manager::BinderRegistry* registry,
-      content::RenderFrameHost* render_frame_host) override;
+  void BindMediaServiceReceiver(content::RenderFrameHost* render_frame_host,
+                                mojo::GenericPendingReceiver receiver) override;
   void RegisterBrowserInterfaceBindersForFrame(
       content::RenderFrameHost* render_frame_host,
       service_manager::BinderMapWithContext<content::RenderFrameHost*>* map)
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateImpl.java
index bfc34f74..fb1d56c5 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/InterceptNavigationDelegateImpl.java
@@ -4,10 +4,13 @@
 
 package org.chromium.weblayer_private;
 
+import android.os.SystemClock;
+
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.components.external_intents.ExternalNavigationHandler;
 import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
 import org.chromium.components.external_intents.ExternalNavigationParams;
+import org.chromium.components.external_intents.RedirectHandlerImpl;
 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
 import org.chromium.components.navigation_interception.NavigationParams;
 import org.chromium.content_public.browser.WebContents;
@@ -18,14 +21,17 @@
  */
 public class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate {
     private TabImpl mTab;
+    private RedirectHandlerImpl mRedirectHandler;
     private ExternalNavigationHandler mExternalNavHandler;
     private ExternalNavigationDelegateImpl mExternalNavigationDelegate;
+    private long mLastNavigationWithUserGestureTime = RedirectHandlerImpl.INVALID_TIME;
 
     /**
      * Default constructor of {@link InterceptNavigationDelegateImpl}.
      */
     InterceptNavigationDelegateImpl(TabImpl tab) {
         mTab = tab;
+        mRedirectHandler = RedirectHandlerImpl.create();
         mExternalNavigationDelegate = new ExternalNavigationDelegateImpl(mTab);
         mExternalNavHandler = new ExternalNavigationHandler(mExternalNavigationDelegate);
         InterceptNavigationDelegateImplJni.get().associateWithWebContents(
@@ -41,15 +47,14 @@
      * ExternalNavigationHandler#shouldOverrideUrlLoading().
      */
     private ExternalNavigationParams.Builder buildExternalNavigationParams(
-            NavigationParams navigationParams, boolean shouldCloseTab) {
+            NavigationParams navigationParams, RedirectHandlerImpl redirectHandler,
+            boolean shouldCloseTab) {
         return new ExternalNavigationParams
                 .Builder(navigationParams.url, mTab.getProfile().isIncognito(),
                         navigationParams.referrer, navigationParams.pageTransitionType,
                         navigationParams.isRedirect)
                 .setApplicationMustBeInForeground(true)
-                // NOTE: ExternalNavigationHandler.java supports the redirect handler being
-                // null, although WebLayer will likely eventually want one.
-                .setRedirectHandler(null)
+                .setRedirectHandler(redirectHandler)
                 .setOpenInNewTab(shouldCloseTab)
                 // TODO(crbug.com/1031465): See whether this needs to become more refined
                 // (cf. //chrome's setting of this field in its version of this method).
@@ -60,6 +65,11 @@
                         shouldCloseTab && navigationParams.isMainFrame);
     }
 
+    private int getLastCommittedEntryIndex() {
+        if (mTab.getWebContents() == null) return -1;
+        return mTab.getWebContents().getNavigationController().getLastCommittedEntryIndex();
+    }
+
     private boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent() {
         // Closing of tabs as part of intent launching is not yet implemented in WebLayer; specify
         // parameters such that this flow is never invoked.
@@ -69,9 +79,48 @@
 
     @Override
     public boolean shouldIgnoreNavigation(NavigationParams navigationParams) {
+        if (navigationParams.hasUserGesture || navigationParams.hasUserGestureCarryover) {
+            mLastNavigationWithUserGestureTime = SystemClock.elapsedRealtime();
+        }
+
+        RedirectHandlerImpl redirectHandler = null;
+        if (navigationParams.isMainFrame) {
+            redirectHandler = mRedirectHandler;
+        } else if (navigationParams.isExternalProtocol) {
+            // Only external protocol navigations are intercepted for iframe navigations.  Since
+            // we do not see all previous navigations for the iframe, we can not build a complete
+            // redirect handler for each iframe.  Nor can we use the top level redirect handler as
+            // that has the potential to incorrectly give access to the navigation due to previous
+            // main frame gestures.
+            //
+            // By creating a new redirect handler for each external navigation, we are specifically
+            // not covering the case where a gesture is carried over via a redirect.  This is
+            // currently not feasible because we do not see all navigations for iframes and it is
+            // better to error on the side of caution and require direct user gestures for iframes.
+            redirectHandler = RedirectHandlerImpl.create();
+        } else {
+            assert false;
+            return false;
+        }
+
+        // NOTE: Chrome listens for user interaction with its Activity. However, this depends on
+        // being able to subclass the Activity, which is not possible in WebLayer. As a proxy,
+        // WebLayer uses the time of the last navigation with a user gesture to serve as the last
+        // time of user interaction. Note that the user interacting with the webpage causes the
+        // user gesture bit to be set on any navigation in that page for the next several seconds
+        // (cf. comments on //third_party/blink/public/common/frame/user_activation_state.h). This
+        // fact further increases the fidelity of this already-reasonable heuristic as a proxy. To
+        // date we have not seen any concrete evidence of user-visible differences resulting from
+        // the use of the different heuristic.
+        redirectHandler.updateNewUrlLoading(navigationParams.pageTransitionType,
+                navigationParams.isRedirect,
+                navigationParams.hasUserGesture || navigationParams.hasUserGestureCarryover,
+                mLastNavigationWithUserGestureTime, getLastCommittedEntryIndex());
+
         boolean shouldCloseTab = shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent();
         ExternalNavigationParams params =
-                buildExternalNavigationParams(navigationParams, shouldCloseTab).build();
+                buildExternalNavigationParams(navigationParams, redirectHandler, shouldCloseTab)
+                        .build();
         return (mExternalNavHandler.shouldOverrideUrlLoading(params)
                 != OverrideUrlLoadingResult.NO_OVERRIDE);
     }
diff --git a/weblayer/browser/profile_disk_operations.cc b/weblayer/browser/profile_disk_operations.cc
new file mode 100644
index 0000000..0edd530
--- /dev/null
+++ b/weblayer/browser/profile_disk_operations.cc
@@ -0,0 +1,91 @@
+// 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 "weblayer/browser/profile_disk_operations.h"
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "weblayer/common/weblayer_paths.h"
+
+namespace weblayer {
+
+namespace {
+
+bool IsNameValid(const std::string& name) {
+  for (char c : name) {
+    if (!(base::IsAsciiDigit(c) || base::IsAsciiAlpha(c) || c == '_'))
+      return false;
+  }
+  return true;
+}
+
+// Return the data path directory to profiles.
+base::FilePath GetProfileRootDataDir() {
+  base::FilePath path;
+  CHECK(base::PathService::Get(DIR_USER_DATA, &path));
+  return path.AppendASCII("profiles");
+}
+
+}  // namespace
+
+ProfileInfo CreateProfileInfo(const std::string& name) {
+  CHECK(IsNameValid(name));
+  if (name.empty())
+    return {name, base::FilePath(), base::FilePath()};
+
+  base::FilePath data_path = GetProfileRootDataDir().AppendASCII(name.c_str());
+  if (!base::PathExists(data_path))
+    base::CreateDirectory(data_path);
+  base::FilePath cache_path = data_path;
+#if defined(OS_POSIX)
+  CHECK(base::PathService::Get(base::DIR_CACHE, &cache_path));
+  cache_path = cache_path.AppendASCII("profiles").AppendASCII(name.c_str());
+  if (!base::PathExists(cache_path))
+    base::CreateDirectory(cache_path);
+#endif
+  return {name, data_path, cache_path};
+}
+
+base::FilePath ComputeBrowserPersisterDataBaseDir(const ProfileInfo& info) {
+  base::FilePath base_path;
+  if (info.data_path.empty()) {
+    CHECK(base::PathService::Get(DIR_USER_DATA, &base_path));
+    base_path = base_path.AppendASCII("Incognito Restore Data");
+  } else {
+    base_path = info.data_path.AppendASCII("Restore Data");
+  }
+  return base_path;
+}
+
+void NukeProfileFromDisk(const ProfileInfo& info) {
+  if (info.name.empty()) {
+    // Incognito. Just delete session data.
+    base::DeleteFileRecursively(ComputeBrowserPersisterDataBaseDir(info));
+    return;
+  }
+
+  base::DeleteFileRecursively(info.data_path);
+#if defined(OS_POSIX)
+  base::DeleteFileRecursively(info.cache_path);
+#endif
+}
+
+std::vector<std::string> ListProfileNames() {
+  base::FilePath root_dir = GetProfileRootDataDir();
+  std::vector<std::string> profile_names;
+  base::FileEnumerator enumerator(root_dir, /*recursive=*/false,
+                                  base::FileEnumerator::DIRECTORIES);
+  for (base::FilePath path = enumerator.Next(); !path.empty();
+       path = enumerator.Next()) {
+    std::string name = enumerator.GetInfo().GetName().MaybeAsASCII();
+    if (IsNameValid(name))
+      profile_names.push_back(name);
+  }
+  return profile_names;
+}
+
+}  // namespace weblayer
diff --git a/weblayer/browser/profile_disk_operations.h b/weblayer/browser/profile_disk_operations.h
new file mode 100644
index 0000000..4ff55b1c
--- /dev/null
+++ b/weblayer/browser/profile_disk_operations.h
@@ -0,0 +1,40 @@
+// 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 WEBLAYER_BROWSER_PROFILE_DISK_OPERATIONS_H_
+#define WEBLAYER_BROWSER_PROFILE_DISK_OPERATIONS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+
+namespace weblayer {
+
+struct ProfileInfo {
+  // The profile name supplied by client code. Name can only contain
+  // alphanumeric and underscore to be valid. The empty name is valid and
+  // indicates the incognito profile.
+  std::string name;
+  // Path where persistent profile data is stored. This will be empty for the
+  // incognito profile with empty name.
+  base::FilePath data_path;
+  // Path where cache profile data is stored. Depending on the OS, this may
+  // be the same as |data_path|; the OS may delete data in this directory.
+  base::FilePath cache_path;
+};
+
+// |name| must be a valid profile name. Ensures that both data and cache path
+// directories are created.
+ProfileInfo CreateProfileInfo(const std::string& name);
+
+base::FilePath ComputeBrowserPersisterDataBaseDir(const ProfileInfo& info);
+void NukeProfileFromDisk(const ProfileInfo& info);
+
+// Return names of profiles on disk. Invalid profile names are ignored.
+std::vector<std::string> ListProfileNames();
+
+}  // namespace weblayer
+
+#endif  // WEBLAYER_BROWSER_PROFILE_DISK_OPERATIONS_H_
diff --git a/weblayer/browser/profile_impl.cc b/weblayer/browser/profile_impl.cc
index e451eeef..34c05c8 100644
--- a/weblayer/browser/profile_impl.cc
+++ b/weblayer/browser/profile_impl.cc
@@ -11,10 +11,6 @@
 
 #include "base/bind.h"
 #include "base/callback_forward.h"
-#include "base/files/file_enumerator.h"
-#include "base/files/file_util.h"
-#include "base/path_service.h"
-#include "base/strings/string_util.h"
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
@@ -32,7 +28,6 @@
 #include "weblayer/browser/browser_context_impl.h"
 #include "weblayer/browser/cookie_manager_impl.h"
 #include "weblayer/browser/tab_impl.h"
-#include "weblayer/common/weblayer_paths.h"
 
 #if defined(OS_ANDROID)
 #include "base/android/callback_android.h"
@@ -54,85 +49,14 @@
 
 namespace {
 
-bool IsNameValid(const std::string& name) {
-  for (size_t i = 0; i < name.size(); ++i) {
-    char c = name[i];
-    if (!(base::IsAsciiDigit(c) || base::IsAsciiAlpha(c) || c == '_')) {
-      return false;
-    }
-  }
-  return true;
-}
-
-// Return the data path directory to profiles.
-base::FilePath GetProfileRootDataDir() {
-  base::FilePath path;
-  CHECK(base::PathService::Get(DIR_USER_DATA, &path));
-  return path.AppendASCII("profiles");
-}
-
-#if defined(OS_POSIX)
-base::FilePath ComputeCachePath(const std::string& profile_name) {
-  base::FilePath path;
-  CHECK(base::PathService::Get(base::DIR_CACHE, &path));
-  return path.AppendASCII("profiles").AppendASCII(profile_name.c_str());
-}
-#endif  // OS_POSIX
-
-base::FilePath ComputeBrowserPersisterDataBaseDir(
-    const base::FilePath& data_path) {
-  base::FilePath base_path;
-  if (data_path.empty()) {
-    CHECK(base::PathService::Get(DIR_USER_DATA, &base_path));
-    base_path = base_path.AppendASCII("Incognito Restore Data");
-  } else {
-    base_path = data_path.AppendASCII("Restore Data");
-  }
-  return base_path;
-}
-
-void NukeProfileFromDisk(const std::string& profile_name,
-                         const base::FilePath& data_path) {
-  if (data_path.empty()) {
-    // Incognito. Just delete session data.
-    base::DeleteFileRecursively(ComputeBrowserPersisterDataBaseDir(data_path));
-    return;
-  }
-
-  base::DeleteFileRecursively(data_path);
-#if defined(OS_POSIX)
-  base::DeleteFileRecursively(ComputeCachePath(profile_name));
-#endif
-}
-
 #if defined(OS_ANDROID)
-// Returned path only contains the directory name.
-// Invalid profile names are ignored.
-std::vector<base::FilePath> ListProfileNames() {
-  base::FilePath root_dir = GetProfileRootDataDir();
-  std::vector<base::FilePath> profile_names;
-  base::FileEnumerator enumerator(root_dir, /*recursive=*/false,
-                                  base::FileEnumerator::DIRECTORIES);
-  for (base::FilePath path = enumerator.Next(); !path.empty();
-       path = enumerator.Next()) {
-    base::FilePath name = enumerator.GetInfo().GetName();
-    if (IsNameValid(name.MaybeAsASCII())) {
-      profile_names.push_back(name);
-    }
-  }
-  return profile_names;
-}
 
 void PassFilePathsToJavaCallback(
     const base::android::ScopedJavaGlobalRef<jobject>& callback,
-    const std::vector<base::FilePath>& file_paths) {
-  std::vector<std::string> strings;
-  for (const auto& path : file_paths) {
-    strings.push_back(path.value());
-  }
+    const std::vector<std::string>& file_paths) {
   base::android::RunObjectCallbackAndroid(
       callback, base::android::ToJavaArrayOfStrings(
-                    base::android::AttachCurrentThread(), strings));
+                    base::android::AttachCurrentThread(), file_paths));
 }
 #endif  // OS_ANDROID
 
@@ -171,33 +95,15 @@
 base::FilePath ProfileImpl::GetCachePath(content::BrowserContext* context) {
   DCHECK(context);
   ProfileImpl* profile = FromBrowserContext(context);
-#if defined(OS_POSIX)
-  base::FilePath path = ComputeCachePath(profile->name_);
-  {
-    base::ScopedAllowBlocking allow_blocking;
-    if (!base::PathExists(path))
-      base::CreateDirectory(path);
-  }
-  return path;
-#else
-  return profile->data_path_;
-#endif
+  return profile->info_.cache_path;
 }
 
 ProfileImpl::ProfileImpl(const std::string& name)
-    : name_(name),
-      download_directory_(BrowserContextImpl::GetDefaultDownloadDirectory()) {
-  if (!name.empty()) {
-    CHECK(IsNameValid(name));
-    {
-      base::ScopedAllowBlocking allow_blocking;
-      data_path_ = GetProfileRootDataDir().AppendASCII(name.c_str());
-
-      if (!base::PathExists(data_path_))
-        base::CreateDirectory(data_path_);
-    }
+    : download_directory_(BrowserContextImpl::GetDefaultDownloadDirectory()) {
+  {
+    base::ScopedAllowBlocking allow_blocking;
+    info_ = CreateProfileInfo(name);
   }
-
   // Ensure WebCacheManager is created so that it starts observing
   // OnRenderProcessHostCreated events.
   web_cache::WebCacheManager::GetInstance();
@@ -218,7 +124,8 @@
   if (browser_context_)
     return browser_context_.get();
 
-  browser_context_ = std::make_unique<BrowserContextImpl>(this, data_path_);
+  browser_context_ =
+      std::make_unique<BrowserContextImpl>(this, info_.data_path);
   locale_change_subscription_ =
       i18n::RegisterLocaleChangeCallback(base::BindRepeating(
           &ProfileImpl::OnLocaleChanged, base::Unretained(this)));
@@ -290,16 +197,14 @@
 // static
 void ProfileImpl::DoNukeData(std::unique_ptr<ProfileImpl> profile,
                              base::OnceClosure done_callback) {
-  std::string name = profile->name_;
-  base::FilePath data_path = profile->data_path_;
+  ProfileInfo info = profile->info_;
 
   profile.reset();
   base::ThreadPool::PostTaskAndReply(
       FROM_HERE,
       {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
        base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
-      base::BindOnce(&NukeProfileFromDisk, name, data_path),
-      std::move(done_callback));
+      base::BindOnce(&NukeProfileFromDisk, info), std::move(done_callback));
 }
 
 void ProfileImpl::ClearRendererCache() {
@@ -463,7 +368,7 @@
 }
 
 base::FilePath ProfileImpl::GetBrowserPersisterDataBaseDir() const {
-  return ComputeBrowserPersisterDataBaseDir(data_path_);
+  return ComputeBrowserPersisterDataBaseDir(info_);
 }
 
 }  // namespace weblayer
diff --git a/weblayer/browser/profile_impl.h b/weblayer/browser/profile_impl.h
index eb920f8..6e4d5ec 100644
--- a/weblayer/browser/profile_impl.h
+++ b/weblayer/browser/profile_impl.h
@@ -10,6 +10,7 @@
 #include "base/memory/weak_ptr.h"
 #include "build/build_config.h"
 #include "weblayer/browser/i18n_util.h"
+#include "weblayer/browser/profile_disk_operations.h"
 #include "weblayer/public/profile.h"
 
 #if defined(OS_ANDROID)
@@ -53,7 +54,7 @@
   void DownloadsInitialized();
 
   // Path data is stored at, empty if off-the-record.
-  const base::FilePath& data_path() const { return data_path_; }
+  const base::FilePath& data_path() const { return info_.data_path; }
   DownloadDelegate* download_delegate() { return download_delegate_; }
 
   // Profile implementation:
@@ -107,9 +108,7 @@
   // Callback when the system locale has been updated.
   void OnLocaleChanged();
 
-  const std::string name_;
-
-  base::FilePath data_path_;
+  ProfileInfo info_;
 
   std::unique_ptr<BrowserContextImpl> browser_context_;